@dev-blinq/cucumber_client 1.0.1562-dev → 1.0.1564-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.
@@ -43,6 +43,7 @@ class PromisifiedSocketServer {
43
43
  }
44
44
  this.socket.emit("response", { id, value: response, roomId, socketId });
45
45
  } catch (error) {
46
+ console.error(`Error handling request for event: ${event}`, error);
46
47
  socketLogger.error("Error handling request", {
47
48
  event,
48
49
  input,
@@ -294,6 +295,22 @@ async function BVTRecorderInit({ envName, projectDir, roomId, TOKEN, socket = nu
294
295
  "recorderWindow.stopRecordingNetwork": async (input) => {
295
296
  return recorder.stopRecordingNetwork(input);
296
297
  },
298
+ "browserUI.requestState": async (input) => {
299
+ console.log("Received browserUI.requestState");
300
+ await recorder.getBrowserState();
301
+ },
302
+ "browserUI.createTab": async (input) => {
303
+ console.log("Received browserUI.createTab", input);
304
+ await recorder.createTab(input);
305
+ },
306
+ "browserUI.closeTab": async (input) => {
307
+ console.log("Received browserUI.closeTab", input);
308
+ await recorder.closeTab(input);
309
+ },
310
+ "browserUI.selectTab": async (input) => {
311
+ console.log("Received browserUI.selectTab", input);
312
+ await recorder.selectTab(input);
313
+ },
297
314
  });
298
315
 
299
316
  socket.on("targetBrowser.command.event", async (input) => {
@@ -211,6 +211,8 @@ export class BVTRecorder {
211
211
  interceptResults: "BVTRecorder.interceptResults",
212
212
  onDebugURLChange: "BVTRecorder.onDebugURLChange",
213
213
  updateCommand: "BVTRecorder.updateCommand",
214
+ browserStateSync: "BrowserService.stateSync",
215
+ browserStateError: "BrowserService.stateError",
214
216
  };
215
217
  bindings = {
216
218
  __bvt_recordCommand: async ({ frame, page, context }, event) => {
@@ -347,19 +349,19 @@ export class BVTRecorder {
347
349
  },
348
350
  bvtContext: this.bvtContext,
349
351
  });
350
- const context = bvtContext.playContext;
351
- this.context = context;
352
+ this.context = bvtContext.playContext;
352
353
  this.web = bvtContext.stable || bvtContext.web;
353
354
  this.web.tryAllStrategies = true;
354
355
  this.page = bvtContext.page;
355
356
  this.pageSet.add(this.page);
356
357
  if (process.env.REMOTE_RECORDER === "true") {
357
- this.remoteBrowser = new RemoteBrowserService({
358
+ this.browserEmitter = new RemoteBrowserService({
358
359
  CDP_CONNECT_URL: `http://localhost:${this.#remoteDebuggerPort}`,
359
- currentPage: this.page,
360
+ context: this.context,
360
361
  });
361
- this.remoteBrowser.on("debugURLChange", (url) => {
362
- this.sendEvent(this.events.onDebugURLChange, url);
362
+ this.browserEmitter.on(this.events.browserStateSync, (state) => {
363
+ this.page = this.browserEmitter.getSelectedPage();
364
+ this.sendEvent(this.events.browserStateSync, state);
363
365
  });
364
366
  }
365
367
  this.lastKnownUrlPath = this._updateUrlPath();
@@ -644,9 +646,6 @@ export class BVTRecorder {
644
646
  try {
645
647
  if (page.isClosed()) return;
646
648
  this.pageSet.add(page);
647
- if (this.remoteBrowser) {
648
- this.remoteBrowser.selectedPage = page;
649
- }
650
649
  await page.waitForLoadState("domcontentloaded");
651
650
 
652
651
  // add listener for frame navigation on new tab
@@ -1332,4 +1331,53 @@ export class BVTRecorder {
1332
1331
 
1333
1332
  return newFakeParams;
1334
1333
  }
1334
+
1335
+ async getBrowserState() {
1336
+ try {
1337
+ const state = await this.browserEmitter?.getState();
1338
+ this.sendEvent(this.events.browserStateSync, state);
1339
+ } catch (error) {
1340
+ this.logger.error("Error getting browser state:", error);
1341
+ this.sendEvent(this.events.browserStateError, {
1342
+ message: "Error getting browser state",
1343
+ code: "GET_STATE_ERROR",
1344
+ });
1345
+ }
1346
+ }
1347
+
1348
+ async createTab(url) {
1349
+ try {
1350
+ await this.browserEmitter?.createTab(url);
1351
+ } catch (error) {
1352
+ this.logger.error("Error creating tab:", error);
1353
+ this.sendEvent(this.events.browserStateError, {
1354
+ message: "Error creating tab",
1355
+ code: "CREATE_TAB_ERROR",
1356
+ });
1357
+ }
1358
+ }
1359
+
1360
+ async closeTab(pageId) {
1361
+ try {
1362
+ await this.browserEmitter?.closeTab(pageId);
1363
+ } catch (error) {
1364
+ this.logger.error("Error closing tab:", error);
1365
+ this.sendEvent(this.events.browserStateError, {
1366
+ message: "Error closing tab",
1367
+ code: "CLOSE_TAB_ERROR",
1368
+ });
1369
+ }
1370
+ }
1371
+
1372
+ async selectTab(pageId) {
1373
+ try {
1374
+ await this.browserEmitter?.selectTab(pageId);
1375
+ } catch (error) {
1376
+ this.logger.error("Error selecting tab:", error);
1377
+ this.sendEvent(this.events.browserStateError, {
1378
+ message: "Error selecting tab",
1379
+ code: "SELECT_TAB_ERROR",
1380
+ });
1381
+ }
1382
+ }
1335
1383
  }
@@ -3,7 +3,6 @@ import { getRunsServiceBaseURL } from "../utils/index.js";
3
3
  import { axiosClient } from "../utils/axiosClient.js";
4
4
  import path from "path";
5
5
  import { EventEmitter } from "events";
6
- import socketLogger from "../utils/socket_logger.js";
7
6
  export class NamesService {
8
7
  screenshotMap;
9
8
  TOKEN;
@@ -157,46 +156,191 @@ export class NamesService {
157
156
  }
158
157
  export class RemoteBrowserService extends EventEmitter {
159
158
  CDP_CONNECT_URL;
160
- _selectedPage;
161
- logger = socketLogger;
162
- constructor({ CDP_CONNECT_URL, currentPage }) {
159
+ context;
160
+ pages = new Map();
161
+ _selectedPageId = null;
162
+ constructor({ CDP_CONNECT_URL, context }) {
163
163
  super();
164
164
  this.CDP_CONNECT_URL = CDP_CONNECT_URL;
165
- this._selectedPage = currentPage;
166
- this.emitSelectedPageId();
165
+ this.context = context;
166
+ this.initializeListeners();
167
167
  }
168
- async emitSelectedPageId() {
169
- let id = null;
168
+ async initializeListeners() {
169
+ // Listen for new pages
170
+ this.context.on("page", async (page) => {
171
+ console.log("New page detected");
172
+ const id = await this.getPageId(page);
173
+ if (id) {
174
+ this.pages.set(id, page);
175
+ console.log(`Page added: ${id}`);
176
+ await this.syncState();
177
+ }
178
+ // Listen for page updates
179
+ page.on("load", async () => {
180
+ console.log("Page loaded");
181
+ await this.syncState();
182
+ });
183
+ page.on("close", async () => {
184
+ console.log(`Page closed: ${id}`);
185
+ if (id) {
186
+ this.pages.delete(id);
187
+ if (this._selectedPageId === id) {
188
+ // Select first available page
189
+ const firstPage = Array.from(this.pages.keys())[0];
190
+ this._selectedPageId = firstPage || null;
191
+ }
192
+ await this.syncState();
193
+ }
194
+ });
195
+ });
196
+ // Initialize with existing pages
197
+ const existingPages = this.context.pages();
198
+ console.log(`Initializing with ${existingPages.length} existing pages`);
199
+ for (const page of existingPages) {
200
+ const id = await this.getPageId(page);
201
+ if (id) {
202
+ this.pages.set(id, page);
203
+ console.log(`Existing page registered: ${id}`);
204
+ }
205
+ }
206
+ // Set initial selected page
207
+ if (this.pages.size > 0 && !this._selectedPageId) {
208
+ this._selectedPageId = Array.from(this.pages.keys())[0];
209
+ }
210
+ await this.syncState();
211
+ }
212
+ async getPageId(page) {
170
213
  try {
171
214
  const debugData = await this.getDebugURLs();
215
+ const pageUrl = page.url();
172
216
  for (const pageData of debugData) {
173
- if (pageData.type === "page" && pageData.url === this._selectedPage.url()) {
174
- id = pageData.id;
175
- break;
217
+ if (pageData.type === "page" && pageData.url === pageUrl) {
218
+ return pageData.id;
219
+ }
220
+ }
221
+ // If exact match fails, try to find by comparing URLs without protocol/www
222
+ const normalizeUrl = (url) => {
223
+ try {
224
+ const u = new URL(url);
225
+ return u.hostname.replace(/^www\./, "") + u.pathname + u.search;
226
+ }
227
+ catch {
228
+ return url;
229
+ }
230
+ };
231
+ const normalizedPageUrl = normalizeUrl(pageUrl);
232
+ for (const pageData of debugData) {
233
+ if (pageData.type === "page" && normalizeUrl(pageData.url) === normalizedPageUrl) {
234
+ return pageData.id;
176
235
  }
177
236
  }
178
- this.emit("debugURLChange", id);
237
+ return null;
179
238
  }
180
239
  catch (error) {
181
- this.logger.error("Error fetching debug URLs: ", error instanceof Error ? error : { message: JSON.stringify(error) });
240
+ console.error("Error getting page ID:", error);
241
+ return null;
182
242
  }
183
243
  }
184
- set selectedPage(page) {
185
- this._selectedPage = page;
186
- this.emitSelectedPageId();
187
- }
188
- get selectedPage() {
189
- return this._selectedPage;
190
- }
191
244
  async getDebugURLs() {
192
245
  const url = `${this.CDP_CONNECT_URL}/json`;
193
- const response = await axiosClient({
194
- url,
195
- method: "GET",
196
- });
197
- if (response.status !== 200) {
246
+ const response = await fetch(url);
247
+ if (!response.ok) {
198
248
  throw new Error("Error while fetching debug URL");
199
249
  }
200
- return response.data;
250
+ return response.json();
251
+ }
252
+ async syncState() {
253
+ try {
254
+ const state = await this.getState();
255
+ console.log(`Syncing state: ${state.pages.length} pages, selected: ${state.selectedPageId}`);
256
+ this.emit("BrowserService.stateSync", state);
257
+ }
258
+ catch (error) {
259
+ console.error("Error syncing state:", error);
260
+ }
261
+ }
262
+ async getState() {
263
+ const debugData = await this.getDebugURLs();
264
+ const pagesData = [];
265
+ for (const [id, page] of this.pages.entries()) {
266
+ const debugInfo = debugData.find((d) => d.id === id);
267
+ if (debugInfo) {
268
+ try {
269
+ pagesData.push({
270
+ id,
271
+ title: await page.title(),
272
+ url: page.url(),
273
+ wsDebuggerUrl: debugInfo.webSocketDebuggerUrl || "",
274
+ });
275
+ }
276
+ catch (error) {
277
+ console.error(`Error getting data for page ${id}:`, error);
278
+ }
279
+ }
280
+ }
281
+ return {
282
+ pages: pagesData,
283
+ selectedPageId: this._selectedPageId,
284
+ };
285
+ }
286
+ async createTab(url = "about:blank") {
287
+ try {
288
+ console.log(`Creating tab: ${url}`);
289
+ const page = await this.context.newPage();
290
+ if (typeof url === "string" && url !== "about:blank") {
291
+ await page.goto(url, { waitUntil: "domcontentloaded" });
292
+ }
293
+ // Wait a bit for the page to be registered in CDP
294
+ await new Promise((resolve) => setTimeout(resolve, 100));
295
+ const id = await this.getPageId(page);
296
+ if (id) {
297
+ this.pages.set(id, page);
298
+ this._selectedPageId = id;
299
+ console.log(`Tab created successfully: ${id}`);
300
+ }
301
+ await this.syncState();
302
+ }
303
+ catch (error) {
304
+ console.error("Error creating tab:", error);
305
+ throw error;
306
+ }
307
+ }
308
+ async closeTab(pageId) {
309
+ try {
310
+ console.log(`Closing tab: ${pageId}`);
311
+ const page = this.pages.get(pageId);
312
+ if (page) {
313
+ await page.close();
314
+ // The close event handler will handle cleanup and sync
315
+ }
316
+ else {
317
+ console.warn(`Page ${pageId} not found`);
318
+ }
319
+ }
320
+ catch (error) {
321
+ console.error("Error closing tab:", error);
322
+ throw error;
323
+ }
324
+ }
325
+ async selectTab(pageId) {
326
+ try {
327
+ console.log(`Selecting tab: ${pageId}`);
328
+ const page = this.pages.get(pageId);
329
+ if (page) {
330
+ this._selectedPageId = pageId;
331
+ await page.bringToFront();
332
+ await this.syncState();
333
+ }
334
+ else {
335
+ console.warn(`Page ${pageId} not found`);
336
+ }
337
+ }
338
+ catch (error) {
339
+ console.error("Error selecting tab:", error);
340
+ throw error;
341
+ }
342
+ }
343
+ getSelectedPage() {
344
+ return this._selectedPageId ? this.pages.get(this._selectedPageId) || null : null;
201
345
  }
202
346
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev-blinq/cucumber_client",
3
- "version": "1.0.1562-dev",
3
+ "version": "1.0.1564-dev",
4
4
  "description": " ",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",