@dev-blinq/cucumber_client 1.0.1562-dev → 1.0.1563-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
|
-
|
|
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.
|
|
358
|
+
this.browserEmitter = new RemoteBrowserService({
|
|
358
359
|
CDP_CONNECT_URL: `http://localhost:${this.#remoteDebuggerPort}`,
|
|
359
|
-
|
|
360
|
+
context: this.context,
|
|
360
361
|
});
|
|
361
|
-
this.
|
|
362
|
-
this.
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
166
|
-
this.
|
|
165
|
+
this.context = context;
|
|
166
|
+
this.initializeListeners();
|
|
167
167
|
}
|
|
168
|
-
async
|
|
169
|
-
|
|
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 ===
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
237
|
+
return null;
|
|
179
238
|
}
|
|
180
239
|
catch (error) {
|
|
181
|
-
|
|
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
|
|
194
|
-
|
|
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.
|
|
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
|
}
|