@dev-blinq/cucumber_client 1.0.1596-dev → 1.0.1598-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.
- package/bin/client/recorderv3/services.js +68 -561
- package/package.json +1 -1
|
@@ -154,7 +154,9 @@ export class PublishService {
|
|
|
154
154
|
this.TOKEN = TOKEN;
|
|
155
155
|
}
|
|
156
156
|
async saveScenario({ scenario, featureName, override, branch, isEditing, projectId }) {
|
|
157
|
-
const
|
|
157
|
+
const runsURL = getRunsServiceBaseURL();
|
|
158
|
+
const workspaceURL = runsURL.replace("/runs", "/workspace");
|
|
159
|
+
const url = `${workspaceURL}/publish-recording`;
|
|
158
160
|
const result = await axiosClient({
|
|
159
161
|
url,
|
|
160
162
|
method: "POST",
|
|
@@ -214,9 +216,8 @@ export class PublishService {
|
|
|
214
216
|
export class RemoteBrowserService extends EventEmitter {
|
|
215
217
|
CDP_CONNECT_URL;
|
|
216
218
|
context;
|
|
217
|
-
// stableTabId, Page Info
|
|
218
219
|
pages = new Map();
|
|
219
|
-
_selectedPageId = null;
|
|
220
|
+
_selectedPageId = null;
|
|
220
221
|
constructor({ CDP_CONNECT_URL, context }) {
|
|
221
222
|
super();
|
|
222
223
|
this.CDP_CONNECT_URL = CDP_CONNECT_URL;
|
|
@@ -228,15 +229,46 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
228
229
|
const timestamp = new Date().toISOString();
|
|
229
230
|
console.log(`[${timestamp}] [RemoteBrowserService] ${message}`, data ? JSON.stringify(data, null, 2) : "");
|
|
230
231
|
}
|
|
232
|
+
// Simplified: Get the ID directly from the page
|
|
233
|
+
async getCdpTargetId(page) {
|
|
234
|
+
try {
|
|
235
|
+
const cdpSession = await page.context().newCDPSession(page);
|
|
236
|
+
const { targetInfo } = await cdpSession.send("Target.getTargetInfo");
|
|
237
|
+
await cdpSession.detach();
|
|
238
|
+
if (targetInfo && targetInfo.targetId) {
|
|
239
|
+
this.log("✅ Found CDP ID by session", { id: targetInfo.targetId, url: page.url() });
|
|
240
|
+
return targetInfo.targetId;
|
|
241
|
+
}
|
|
242
|
+
throw new Error("Target.getTargetInfo did not return a targetId");
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
this.log("❌ Error getting CDP ID by session", { url: page.url(), error });
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async getDebugURLs() {
|
|
250
|
+
const url = `${this.CDP_CONNECT_URL}/json?t=${Date.now()}`; // Cache-busting
|
|
251
|
+
try {
|
|
252
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
this.log("❌ Failed to fetch debug URLs", { status: response.status });
|
|
255
|
+
throw new Error("Error while fetching debug URL");
|
|
256
|
+
}
|
|
257
|
+
return await response.json();
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
this.log("❌ Exception while fetching debug URLs", { error });
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
231
264
|
async initializeListeners() {
|
|
232
265
|
this.log("📡 Initializing listeners");
|
|
233
266
|
this.context.on("page", async (page) => {
|
|
234
267
|
const stableTabId = uuidv4();
|
|
235
268
|
this.log("🆕 New page event triggered", { stableTabId, url: page.url() });
|
|
236
|
-
this.
|
|
237
|
-
|
|
269
|
+
const cdpTargetId = await this.getCdpTargetId(page);
|
|
270
|
+
this.pages.set(stableTabId, { page, cdpTargetId });
|
|
238
271
|
if (cdpTargetId) {
|
|
239
|
-
this.pages.get(stableTabId).cdpTargetId = cdpTargetId;
|
|
240
272
|
this.log("✅ Page mapped to CDP ID", { stableTabId, cdpTargetId });
|
|
241
273
|
}
|
|
242
274
|
else {
|
|
@@ -246,115 +278,37 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
246
278
|
this._selectedPageId = stableTabId;
|
|
247
279
|
}
|
|
248
280
|
await this.syncState();
|
|
249
|
-
page.on("load",
|
|
250
|
-
|
|
251
|
-
const newCdpId = await this.findCdpTargetId(page);
|
|
252
|
-
const pageInfo = this.pages.get(stableTabId);
|
|
253
|
-
if (pageInfo && newCdpId && pageInfo.cdpTargetId !== newCdpId) {
|
|
254
|
-
this.log("🔄 CDP Target ID changed on navigation", {
|
|
255
|
-
stableTabId,
|
|
256
|
-
old: pageInfo.cdpTargetId,
|
|
257
|
-
new: newCdpId,
|
|
258
|
-
});
|
|
259
|
-
pageInfo.cdpTargetId = newCdpId;
|
|
260
|
-
}
|
|
261
|
-
await this.syncState();
|
|
262
|
-
});
|
|
281
|
+
page.on("load", () => this.syncState());
|
|
282
|
+
page.on("framenavigated", () => this.syncState());
|
|
263
283
|
page.on("close", async () => {
|
|
264
|
-
this.log("🗑️ Page close event", { stableTabId
|
|
284
|
+
this.log("🗑️ Page close event", { stableTabId });
|
|
265
285
|
this.pages.delete(stableTabId);
|
|
266
|
-
this.log("✅ Page removed from internal map", {
|
|
267
|
-
stableTabId,
|
|
268
|
-
remaining: this.pages.size,
|
|
269
|
-
});
|
|
270
286
|
if (this._selectedPageId === stableTabId) {
|
|
271
|
-
|
|
272
|
-
this.
|
|
273
|
-
this.log("🔄 Selected page changed after close", {
|
|
274
|
-
oldSelectedId: stableTabId,
|
|
275
|
-
newSelectedId: this._selectedPageId,
|
|
276
|
-
});
|
|
287
|
+
this._selectedPageId = this.pages.size > 0 ? Array.from(this.pages.keys())[0] : null;
|
|
288
|
+
this.log("🔄 Selected page changed after close", { newSelectedId: this._selectedPageId });
|
|
277
289
|
}
|
|
278
290
|
await this.syncState();
|
|
279
291
|
});
|
|
280
|
-
page.on("framenavigated", async (frame) => {
|
|
281
|
-
this.log("🔄 Frame navigated event", { stableTabId, url: frame.url() });
|
|
282
|
-
await this.syncState();
|
|
283
|
-
});
|
|
284
292
|
});
|
|
293
|
+
// Initialize with existing pages
|
|
285
294
|
const existingPages = this.context.pages();
|
|
286
295
|
this.log("📄 Found existing pages", { count: existingPages.length });
|
|
287
296
|
for (const page of existingPages) {
|
|
288
297
|
const stableTabId = uuidv4();
|
|
289
|
-
const cdpTargetId = await this.
|
|
298
|
+
const cdpTargetId = await this.getCdpTargetId(page);
|
|
290
299
|
this.pages.set(stableTabId, { page, cdpTargetId });
|
|
291
|
-
this.log("✅ Existing page added to map", {
|
|
292
|
-
stableTabId,
|
|
293
|
-
cdpTargetId,
|
|
294
|
-
url: page.url(),
|
|
295
|
-
});
|
|
300
|
+
this.log("✅ Existing page added to map", { stableTabId, cdpTargetId, url: page.url() });
|
|
296
301
|
}
|
|
297
302
|
if (this.pages.size > 0 && !this._selectedPageId) {
|
|
298
303
|
this._selectedPageId = Array.from(this.pages.keys())[0];
|
|
299
|
-
this.log("🎯 Initial selected page set", { selectedPageId: this._selectedPageId });
|
|
300
304
|
}
|
|
301
305
|
await this.syncState();
|
|
302
306
|
}
|
|
303
|
-
async findCdpTargetId(page) {
|
|
304
|
-
try {
|
|
305
|
-
// This is the most direct and reliable way to get the ID
|
|
306
|
-
const cdpSession = await page.context().newCDPSession(page);
|
|
307
|
-
const { targetInfo } = await cdpSession.send("Target.getTargetInfo");
|
|
308
|
-
await cdpSession.detach();
|
|
309
|
-
if (targetInfo && targetInfo.targetId) {
|
|
310
|
-
this.log("🎭 Target info", targetInfo);
|
|
311
|
-
this.log("✅ Found CDP ID by session", { id: targetInfo.targetId, url: page.url() });
|
|
312
|
-
return targetInfo.targetId;
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
throw new Error("Target.getTargetInfo did not return a targetId");
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
catch (cdpError) {
|
|
319
|
-
// If the session fails, fall back to a simple URL match
|
|
320
|
-
this.log("⚠️ CDP session failed, trying URL match as fallback...", { url: page.url(), cdpError });
|
|
321
|
-
try {
|
|
322
|
-
const debugData = await this.getDebugURLs(); // This now has the cache-busting fix
|
|
323
|
-
const match = debugData.find((p) => p.type === "page" && p.url === page.url());
|
|
324
|
-
if (match) {
|
|
325
|
-
this.log("✅ Found CDP ID by exact URL as fallback", { id: match.id, url: page.url() });
|
|
326
|
-
return match.id;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
catch (fetchError) {
|
|
330
|
-
this.log("❌ Error in fallback getDebugURLs", { fetchError });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
this.log("❌ No match found for page", { url: page.url() });
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
async getDebugURLs() {
|
|
337
|
-
const url = `${this.CDP_CONNECT_URL}/json?_=${Date.now()}`; // Cache-busting query param
|
|
338
|
-
try {
|
|
339
|
-
const response = await fetch(url, { cache: "no-store" });
|
|
340
|
-
if (!response.ok) {
|
|
341
|
-
throw new Error("Error while fetching debug URL");
|
|
342
|
-
}
|
|
343
|
-
return await response.json();
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
this.log("❌ Exception while fetching debug URLs", { error });
|
|
347
|
-
return [];
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
307
|
async syncState() {
|
|
351
308
|
try {
|
|
352
309
|
this.log("🔄 Starting state sync");
|
|
353
310
|
const state = await this.getState();
|
|
354
|
-
this.log("✅ State sync complete", {
|
|
355
|
-
pagesCount: state.pages.length,
|
|
356
|
-
selectedPageId: state.selectedPageId,
|
|
357
|
-
});
|
|
311
|
+
this.log("✅ State sync complete", { pagesCount: state.pages.length, selectedPageId: state.selectedPageId });
|
|
358
312
|
this.emit("BrowserService.stateSync", state);
|
|
359
313
|
}
|
|
360
314
|
catch (error) {
|
|
@@ -363,38 +317,36 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
363
317
|
}
|
|
364
318
|
async getState() {
|
|
365
319
|
this.log("📊 Getting current state");
|
|
320
|
+
// Get the *live* list of debuggable pages
|
|
366
321
|
const debugData = await this.getDebugURLs();
|
|
367
|
-
|
|
322
|
+
const debugMap = new Map(debugData.map((d) => [d.id, d]));
|
|
368
323
|
const pagesData = [];
|
|
369
324
|
for (const [stableTabId, pageInfo] of this.pages.entries()) {
|
|
370
|
-
let currentCdpId = pageInfo.cdpTargetId;
|
|
371
|
-
let debugInfo = debugData.find((d) => d.id === currentCdpId);
|
|
372
|
-
this.log("🔍 debugInfo ", debugInfo);
|
|
373
|
-
if (!debugInfo) {
|
|
374
|
-
this.log("⚠️ Re-finding CDP ID for", { stableTabId });
|
|
375
|
-
const newCdpId = await this.findCdpTargetId(pageInfo.page);
|
|
376
|
-
if (newCdpId) {
|
|
377
|
-
pageInfo.cdpTargetId = newCdpId;
|
|
378
|
-
debugInfo = debugData.find((d) => d.id === newCdpId);
|
|
379
|
-
this.log("🔍 Updated debugInfo after re-finding CDP ID", debugInfo);
|
|
380
|
-
this.log("✅ Re-found CDP ID", { stableTabId, newCdpId });
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
325
|
try {
|
|
326
|
+
// Re-verify the CDP ID in case it changed
|
|
327
|
+
const currentCdpId = await this.getCdpTargetId(pageInfo.page);
|
|
328
|
+
if (currentCdpId && pageInfo.cdpTargetId !== currentCdpId) {
|
|
329
|
+
this.log("🔄 CDP ID changed", { stableTabId, old: pageInfo.cdpTargetId, new: currentCdpId });
|
|
330
|
+
pageInfo.cdpTargetId = currentCdpId;
|
|
331
|
+
}
|
|
332
|
+
// Get the debug info from the live map
|
|
333
|
+
const debugInfo = currentCdpId ? debugMap.get(currentCdpId) : undefined;
|
|
384
334
|
pagesData.push({
|
|
385
|
-
id: stableTabId,
|
|
335
|
+
id: stableTabId,
|
|
386
336
|
title: await pageInfo.page.title(),
|
|
387
337
|
url: pageInfo.page.url(),
|
|
388
|
-
wsDebuggerUrl: debugInfo?.webSocketDebuggerUrl || "",
|
|
338
|
+
wsDebuggerUrl: debugInfo?.webSocketDebuggerUrl || "",
|
|
389
339
|
});
|
|
390
340
|
}
|
|
391
341
|
catch (error) {
|
|
342
|
+
// This likely means the page was closed during the loop
|
|
392
343
|
this.log("❌ Error getting page data (page might be closed)", { stableTabId });
|
|
344
|
+
this.pages.delete(stableTabId); // Clean up dead page
|
|
393
345
|
}
|
|
394
346
|
}
|
|
347
|
+
// Ensure selectedPageId is valid
|
|
395
348
|
if (this._selectedPageId && !this.pages.has(this._selectedPageId)) {
|
|
396
|
-
this._selectedPageId =
|
|
397
|
-
this.log("🔄 Corrected selectedPageId", { new: this._selectedPageId });
|
|
349
|
+
this._selectedPageId = pagesData.length > 0 ? pagesData[0].id : null;
|
|
398
350
|
}
|
|
399
351
|
const state = {
|
|
400
352
|
pages: pagesData,
|
|
@@ -406,10 +358,11 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
406
358
|
async createTab(url = "about:blank") {
|
|
407
359
|
try {
|
|
408
360
|
this.log("🆕 Creating new tab", { url });
|
|
409
|
-
const page = await this.context.newPage(); //
|
|
361
|
+
const page = await this.context.newPage(); // Triggers 'page' event
|
|
410
362
|
if (url !== "about:blank") {
|
|
411
363
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
412
364
|
}
|
|
365
|
+
// Find the stableTabId that was just created by the 'page' event
|
|
413
366
|
for (const [stableTabId, pageInfo] of this.pages.entries()) {
|
|
414
367
|
if (pageInfo.page === page) {
|
|
415
368
|
this._selectedPageId = stableTabId;
|
|
@@ -428,7 +381,7 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
428
381
|
this.log("🗑️ Closing tab", { stableTabId });
|
|
429
382
|
const pageInfo = this.pages.get(stableTabId);
|
|
430
383
|
if (pageInfo) {
|
|
431
|
-
await pageInfo.page.close(); //
|
|
384
|
+
await pageInfo.page.close(); // Triggers 'close' event
|
|
432
385
|
}
|
|
433
386
|
else {
|
|
434
387
|
this.log("⚠️ Page not found for closing", { stableTabId });
|
|
@@ -458,462 +411,16 @@ export class RemoteBrowserService extends EventEmitter {
|
|
|
458
411
|
}
|
|
459
412
|
getSelectedPage() {
|
|
460
413
|
const pageInfo = this._selectedPageId ? this.pages.get(this._selectedPageId) : null;
|
|
461
|
-
this.log("🔍 Getting selected page", {
|
|
462
|
-
selectedPageId: this._selectedPageId,
|
|
463
|
-
found: !!pageInfo,
|
|
464
|
-
url: pageInfo?.page.url(),
|
|
465
|
-
});
|
|
466
414
|
return pageInfo?.page || null;
|
|
467
415
|
}
|
|
468
416
|
destroy() {
|
|
469
417
|
this.log("💥 Destroying RemoteBrowserService");
|
|
470
|
-
// Remove all listeners *this* instance has registered
|
|
471
|
-
// This stops it from listening to context/page events
|
|
472
418
|
this.context.removeAllListeners("page");
|
|
473
|
-
for (const [
|
|
474
|
-
pageInfo.page.removeAllListeners(
|
|
475
|
-
pageInfo.page.removeAllListeners("close");
|
|
476
|
-
pageInfo.page.removeAllListeners("framenavigated");
|
|
419
|
+
for (const [, pageInfo] of this.pages.entries()) {
|
|
420
|
+
pageInfo.page.removeAllListeners();
|
|
477
421
|
}
|
|
478
|
-
// Clear internal maps
|
|
479
422
|
this.pages.clear();
|
|
480
423
|
this._selectedPageId = null;
|
|
481
|
-
// Remove all listeners *on this* emitter
|
|
482
424
|
this.removeAllListeners();
|
|
483
425
|
}
|
|
484
426
|
}
|
|
485
|
-
// export class RemoteBrowserService extends EventEmitter {
|
|
486
|
-
// private CDP_CONNECT_URL: string;
|
|
487
|
-
// private context: BrowserContext;
|
|
488
|
-
// private pages: Map<string, Page> = new Map();
|
|
489
|
-
// private _selectedPageId: string | null = null;
|
|
490
|
-
// constructor({ CDP_CONNECT_URL, context }: { CDP_CONNECT_URL: string; context: BrowserContext }) {
|
|
491
|
-
// super();
|
|
492
|
-
// this.CDP_CONNECT_URL = CDP_CONNECT_URL;
|
|
493
|
-
// this.context = context;
|
|
494
|
-
// this.log("🚀 RemoteBrowserService initialized", { CDP_CONNECT_URL });
|
|
495
|
-
// this.initializeListeners();
|
|
496
|
-
// }
|
|
497
|
-
// private log(message: string, data?: any) {
|
|
498
|
-
// const timestamp = new Date().toISOString();
|
|
499
|
-
// console.log(`[${timestamp}] [RemoteBrowserService] ${message}`, data ? JSON.stringify(data, null, 2) : "");
|
|
500
|
-
// }
|
|
501
|
-
// private async initializeListeners() {
|
|
502
|
-
// this.log("📡 Initializing listeners");
|
|
503
|
-
// // Listen for new pages
|
|
504
|
-
// this.context.on("page", async (page) => {
|
|
505
|
-
// this.log("🆕 New page event triggered", { url: page.url() });
|
|
506
|
-
// const id = await this.getPageId(page);
|
|
507
|
-
// this.log("🔍 Got page ID from CDP", { id, url: page.url() });
|
|
508
|
-
// if (id) {
|
|
509
|
-
// this.pages.set(id, page);
|
|
510
|
-
// this.log("✅ Page added to internal map", {
|
|
511
|
-
// id,
|
|
512
|
-
// url: page.url(),
|
|
513
|
-
// totalPages: this.pages.size,
|
|
514
|
-
// allPageIds: Array.from(this.pages.keys()),
|
|
515
|
-
// });
|
|
516
|
-
// await this.syncState();
|
|
517
|
-
// } else {
|
|
518
|
-
// this.log("❌ Failed to get page ID, page not added to map", { url: page.url() });
|
|
519
|
-
// }
|
|
520
|
-
// // Listen for page updates
|
|
521
|
-
// page.on("load", async () => {
|
|
522
|
-
// this.log("🔄 Page load event", { id, url: page.url() });
|
|
523
|
-
// await this.syncState();
|
|
524
|
-
// });
|
|
525
|
-
// page.on("close", async () => {
|
|
526
|
-
// this.log("🗑️ Page close event", { id, url: page.url() });
|
|
527
|
-
// if (id) {
|
|
528
|
-
// this.pages.delete(id);
|
|
529
|
-
// this.log("✅ Page removed from internal map", {
|
|
530
|
-
// id,
|
|
531
|
-
// remainingPages: this.pages.size,
|
|
532
|
-
// allPageIds: Array.from(this.pages.keys()),
|
|
533
|
-
// });
|
|
534
|
-
// if (this._selectedPageId === id) {
|
|
535
|
-
// const firstPage = Array.from(this.pages.keys())[0];
|
|
536
|
-
// this._selectedPageId = firstPage || null;
|
|
537
|
-
// this.log("🔄 Selected page changed after close", {
|
|
538
|
-
// oldSelectedId: id,
|
|
539
|
-
// newSelectedId: this._selectedPageId,
|
|
540
|
-
// });
|
|
541
|
-
// }
|
|
542
|
-
// await this.syncState();
|
|
543
|
-
// }
|
|
544
|
-
// });
|
|
545
|
-
// });
|
|
546
|
-
// // Initialize with existing pages
|
|
547
|
-
// const existingPages = this.context.pages();
|
|
548
|
-
// this.log("📄 Found existing pages", { count: existingPages.length });
|
|
549
|
-
// for (const page of existingPages) {
|
|
550
|
-
// const id = await this.getPageId(page);
|
|
551
|
-
// this.log("🔍 Processing existing page", { id, url: page.url() });
|
|
552
|
-
// if (id) {
|
|
553
|
-
// this.pages.set(id, page);
|
|
554
|
-
// this.log("✅ Existing page added to map", {
|
|
555
|
-
// id,
|
|
556
|
-
// url: page.url(),
|
|
557
|
-
// totalPages: this.pages.size,
|
|
558
|
-
// });
|
|
559
|
-
// }
|
|
560
|
-
// }
|
|
561
|
-
// // Set initial selected page
|
|
562
|
-
// if (this.pages.size > 0 && !this._selectedPageId) {
|
|
563
|
-
// this._selectedPageId = Array.from(this.pages.keys())[0];
|
|
564
|
-
// this.log("🎯 Initial selected page set", { selectedPageId: this._selectedPageId });
|
|
565
|
-
// }
|
|
566
|
-
// this.log("✅ Initialization complete", {
|
|
567
|
-
// totalPages: this.pages.size,
|
|
568
|
-
// selectedPageId: this._selectedPageId,
|
|
569
|
-
// allPageIds: Array.from(this.pages.keys()),
|
|
570
|
-
// });
|
|
571
|
-
// await this.syncState();
|
|
572
|
-
// }
|
|
573
|
-
// private async getPageId(page: Page): Promise<string | null> {
|
|
574
|
-
// try {
|
|
575
|
-
// const pageUrl = page.url();
|
|
576
|
-
// this.log("🔍 Getting page ID", { pageUrl });
|
|
577
|
-
// // Fetch debug data from /json endpoint (more reliable for matching)
|
|
578
|
-
// const debugData = await this.getDebugURLs();
|
|
579
|
-
// this.log("📊 CDP debug data received", {
|
|
580
|
-
// totalPages: debugData.length,
|
|
581
|
-
// pages: debugData.map((p) => ({ id: p.id, type: p.type, url: p.url })),
|
|
582
|
-
// });
|
|
583
|
-
// // Exact URL match
|
|
584
|
-
// for (const pageData of debugData) {
|
|
585
|
-
// if (pageData.type === "page" && pageData.url === pageUrl) {
|
|
586
|
-
// this.log("✅ Found exact URL match in /json", {
|
|
587
|
-
// id: pageData.id,
|
|
588
|
-
// url: pageUrl,
|
|
589
|
-
// });
|
|
590
|
-
// return pageData.id;
|
|
591
|
-
// }
|
|
592
|
-
// }
|
|
593
|
-
// this.log("⚠️ No exact match found, trying normalized URLs", { pageUrl });
|
|
594
|
-
// // Normalized URL match
|
|
595
|
-
// const normalizeUrl = (url: string) => {
|
|
596
|
-
// try {
|
|
597
|
-
// const u = new URL(url);
|
|
598
|
-
// const normalized = u.hostname.replace(/^www\./, "") + u.pathname + u.search;
|
|
599
|
-
// return normalized;
|
|
600
|
-
// } catch {
|
|
601
|
-
// return url;
|
|
602
|
-
// }
|
|
603
|
-
// };
|
|
604
|
-
// const normalizedPageUrl = normalizeUrl(pageUrl);
|
|
605
|
-
// for (const pageData of debugData) {
|
|
606
|
-
// if (pageData.type === "page") {
|
|
607
|
-
// const normalizedDebugUrl = normalizeUrl(pageData.url);
|
|
608
|
-
// if (normalizedDebugUrl === normalizedPageUrl) {
|
|
609
|
-
// this.log("✅ Found normalized URL match in /json", {
|
|
610
|
-
// id: pageData.id,
|
|
611
|
-
// pageUrl: normalizedPageUrl,
|
|
612
|
-
// debugUrl: normalizedDebugUrl,
|
|
613
|
-
// });
|
|
614
|
-
// return pageData.id;
|
|
615
|
-
// }
|
|
616
|
-
// }
|
|
617
|
-
// }
|
|
618
|
-
// // If still not found, try CDP session as fallback
|
|
619
|
-
// this.log("⚠️ Not found in /json, trying CDP Target.getTargetInfo", { pageUrl });
|
|
620
|
-
// try {
|
|
621
|
-
// const cdpSession = await page.context().newCDPSession(page);
|
|
622
|
-
// const { targetInfo } = await cdpSession.send("Target.getTargetInfo");
|
|
623
|
-
// await cdpSession.detach();
|
|
624
|
-
// if (targetInfo && targetInfo.targetId) {
|
|
625
|
-
// // Verify this target ID exists in debug data
|
|
626
|
-
// const targetExists = debugData.some((d) => d.id === targetInfo.targetId);
|
|
627
|
-
// if (targetExists) {
|
|
628
|
-
// this.log("✅ Got target ID from CDP session and verified in /json", {
|
|
629
|
-
// targetId: targetInfo.targetId,
|
|
630
|
-
// url: pageUrl,
|
|
631
|
-
// });
|
|
632
|
-
// return targetInfo.targetId;
|
|
633
|
-
// } else {
|
|
634
|
-
// this.log("⚠️ Target ID from CDP session not found in /json (ID mismatch)", {
|
|
635
|
-
// cdpTargetId: targetInfo.targetId,
|
|
636
|
-
// availableIds: debugData.map((d) => d.id),
|
|
637
|
-
// });
|
|
638
|
-
// }
|
|
639
|
-
// }
|
|
640
|
-
// } catch (cdpError) {
|
|
641
|
-
// this.log("⚠️ Failed to get target ID from CDP session", {
|
|
642
|
-
// error: cdpError instanceof Error ? cdpError.message : String(cdpError),
|
|
643
|
-
// });
|
|
644
|
-
// }
|
|
645
|
-
// this.log("❌ No match found for page", {
|
|
646
|
-
// pageUrl,
|
|
647
|
-
// normalizedPageUrl,
|
|
648
|
-
// availablePages: debugData.filter((p) => p.type === "page").map((p) => p.url),
|
|
649
|
-
// });
|
|
650
|
-
// return null;
|
|
651
|
-
// } catch (error) {
|
|
652
|
-
// this.log("❌ Error getting page ID", {
|
|
653
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
654
|
-
// stack: error instanceof Error ? error.stack : undefined,
|
|
655
|
-
// });
|
|
656
|
-
// return null;
|
|
657
|
-
// }
|
|
658
|
-
// }
|
|
659
|
-
// private async getDebugURLs(): Promise<DebugPageInfo[]> {
|
|
660
|
-
// const url = `${this.CDP_CONNECT_URL}/json`;
|
|
661
|
-
// this.log("📡 Fetching debug URLs", { url });
|
|
662
|
-
// try {
|
|
663
|
-
// const response = await fetch(url);
|
|
664
|
-
// if (!response.ok) {
|
|
665
|
-
// this.log("❌ Failed to fetch debug URLs", {
|
|
666
|
-
// status: response.status,
|
|
667
|
-
// statusText: response.statusText,
|
|
668
|
-
// });
|
|
669
|
-
// throw new Error("Error while fetching debug URL");
|
|
670
|
-
// }
|
|
671
|
-
// const data = await response.json();
|
|
672
|
-
// this.log("✅ Debug URLs fetched successfully", {
|
|
673
|
-
// count: data.length,
|
|
674
|
-
// pages: data.map((p: any) => ({ id: p.id, type: p.type, url: p.url })),
|
|
675
|
-
// });
|
|
676
|
-
// return data;
|
|
677
|
-
// } catch (error) {
|
|
678
|
-
// this.log("❌ Exception while fetching debug URLs", {
|
|
679
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
680
|
-
// });
|
|
681
|
-
// throw error;
|
|
682
|
-
// }
|
|
683
|
-
// }
|
|
684
|
-
// private async syncState() {
|
|
685
|
-
// try {
|
|
686
|
-
// this.log("🔄 Starting state sync");
|
|
687
|
-
// const state = await this.getState();
|
|
688
|
-
// this.log("✅ State sync complete", {
|
|
689
|
-
// pagesCount: state.pages.length,
|
|
690
|
-
// selectedPageId: state.selectedPageId,
|
|
691
|
-
// pages: state.pages.map((p) => ({ id: p.id, title: p.title, url: p.url })),
|
|
692
|
-
// });
|
|
693
|
-
// this.emit("BrowserService.stateSync", state);
|
|
694
|
-
// } catch (error) {
|
|
695
|
-
// this.log("❌ Error syncing state", {
|
|
696
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
697
|
-
// stack: error instanceof Error ? error.stack : undefined,
|
|
698
|
-
// });
|
|
699
|
-
// }
|
|
700
|
-
// }
|
|
701
|
-
// async getState(): Promise<BrowserState> {
|
|
702
|
-
// this.log("📊 Getting current state", {
|
|
703
|
-
// internalPagesCount: this.pages.size,
|
|
704
|
-
// internalPageIds: Array.from(this.pages.keys()),
|
|
705
|
-
// selectedPageId: this._selectedPageId,
|
|
706
|
-
// });
|
|
707
|
-
// const debugData = await this.getDebugURLs();
|
|
708
|
-
// this.log("📊 Debug data for state", {
|
|
709
|
-
// debugPagesCount: debugData.length,
|
|
710
|
-
// debugPages: debugData.map((p) => ({ id: p.id, type: p.type, url: p.url })),
|
|
711
|
-
// });
|
|
712
|
-
// const pagesData: PageData[] = [];
|
|
713
|
-
// const updatedPages = new Map<string, Page>();
|
|
714
|
-
// const matchedDebugIds = new Set<string>(); // Track which CDP IDs we've already matched
|
|
715
|
-
// let updatedSelectedPageId = this._selectedPageId;
|
|
716
|
-
// for (const [oldId, page] of this.pages.entries()) {
|
|
717
|
-
// this.log("🔍 Processing page from internal map", {
|
|
718
|
-
// oldId,
|
|
719
|
-
// url: page.url(),
|
|
720
|
-
// });
|
|
721
|
-
// // Try to find by old ID first (most common case - ID hasn't changed)
|
|
722
|
-
// let debugInfo = debugData.find((d) => d.id === oldId && !matchedDebugIds.has(d.id));
|
|
723
|
-
// // Fallback: Try to find by URL (page ID may have changed)
|
|
724
|
-
// if (!debugInfo) {
|
|
725
|
-
// this.log("⚠️ Page ID not found in CDP, attempting to match by URL", {
|
|
726
|
-
// oldId,
|
|
727
|
-
// pageUrl: page.url(),
|
|
728
|
-
// });
|
|
729
|
-
// debugInfo = debugData.find((d) => d.type === "page" && d.url === page.url() && !matchedDebugIds.has(d.id));
|
|
730
|
-
// if (debugInfo) {
|
|
731
|
-
// this.log("✅ Found page by URL match, updating ID", {
|
|
732
|
-
// oldId,
|
|
733
|
-
// newId: debugInfo.id,
|
|
734
|
-
// url: page.url(),
|
|
735
|
-
// });
|
|
736
|
-
// // Update selected page ID if this was the selected page and ID changed
|
|
737
|
-
// if (oldId === this._selectedPageId) {
|
|
738
|
-
// updatedSelectedPageId = debugInfo.id;
|
|
739
|
-
// this.log("🔄 Updated selected page ID", {
|
|
740
|
-
// oldId,
|
|
741
|
-
// newId: debugInfo.id,
|
|
742
|
-
// });
|
|
743
|
-
// }
|
|
744
|
-
// }
|
|
745
|
-
// } else {
|
|
746
|
-
// this.log("✅ Found matching debug info by ID", {
|
|
747
|
-
// id: debugInfo.id,
|
|
748
|
-
// debugUrl: debugInfo.url,
|
|
749
|
-
// pageUrl: page.url(),
|
|
750
|
-
// });
|
|
751
|
-
// }
|
|
752
|
-
// if (debugInfo) {
|
|
753
|
-
// // Mark this CDP ID as matched to avoid duplicate matches
|
|
754
|
-
// matchedDebugIds.add(debugInfo.id);
|
|
755
|
-
// try {
|
|
756
|
-
// const pageData = {
|
|
757
|
-
// id: debugInfo.id,
|
|
758
|
-
// title: await page.title(),
|
|
759
|
-
// url: page.url(),
|
|
760
|
-
// wsDebuggerUrl: debugInfo.webSocketDebuggerUrl || "",
|
|
761
|
-
// };
|
|
762
|
-
// pagesData.push(pageData);
|
|
763
|
-
// updatedPages.set(debugInfo.id, page);
|
|
764
|
-
// this.log("✅ Page added to state", pageData);
|
|
765
|
-
// } catch (error) {
|
|
766
|
-
// this.log("❌ Error getting page data", {
|
|
767
|
-
// id: oldId,
|
|
768
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
769
|
-
// });
|
|
770
|
-
// }
|
|
771
|
-
// } else {
|
|
772
|
-
// this.log("⚠️ No matching debug info found by ID or URL", {
|
|
773
|
-
// oldId,
|
|
774
|
-
// pageUrl: page.url(),
|
|
775
|
-
// availableDebugIds: debugData.map((d) => d.id),
|
|
776
|
-
// availableDebugUrls: debugData.map((d) => d.url),
|
|
777
|
-
// alreadyMatched: Array.from(matchedDebugIds),
|
|
778
|
-
// });
|
|
779
|
-
// }
|
|
780
|
-
// }
|
|
781
|
-
// // Update the internal pages map with current CDP IDs
|
|
782
|
-
// if (updatedPages.size !== this.pages.size || updatedSelectedPageId !== this._selectedPageId) {
|
|
783
|
-
// this.log("🔄 Updating internal state with new CDP IDs", {
|
|
784
|
-
// oldPagesCount: this.pages.size,
|
|
785
|
-
// newPagesCount: updatedPages.size,
|
|
786
|
-
// oldSelectedId: this._selectedPageId,
|
|
787
|
-
// newSelectedId: updatedSelectedPageId,
|
|
788
|
-
// });
|
|
789
|
-
// this.pages = updatedPages;
|
|
790
|
-
// this._selectedPageId = updatedSelectedPageId;
|
|
791
|
-
// }
|
|
792
|
-
// const state = {
|
|
793
|
-
// pages: pagesData,
|
|
794
|
-
// selectedPageId: this._selectedPageId,
|
|
795
|
-
// };
|
|
796
|
-
// this.log("📦 Final state", state);
|
|
797
|
-
// return state;
|
|
798
|
-
// }
|
|
799
|
-
// async createTab(url: string = "about:blank"): Promise<void> {
|
|
800
|
-
// try {
|
|
801
|
-
// this.log("🆕 Creating new tab", { url });
|
|
802
|
-
// const page = await this.context.newPage();
|
|
803
|
-
// this.log("✅ New page created by Playwright", { url: page.url() });
|
|
804
|
-
// if (typeof url === "string" && url !== "about:blank") {
|
|
805
|
-
// this.log("🌐 Navigating to URL", { url });
|
|
806
|
-
// await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
807
|
-
// this.log("✅ Navigation complete", { finalUrl: page.url() });
|
|
808
|
-
// }
|
|
809
|
-
// // Wait longer for CDP to register and stabilize the page
|
|
810
|
-
// this.log("⏳ Waiting for CDP registration and stabilization...");
|
|
811
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000)); // Increased to 1 second
|
|
812
|
-
// // Try multiple times to get a stable page ID
|
|
813
|
-
// let id: string | null = null;
|
|
814
|
-
// let attempts = 0;
|
|
815
|
-
// const maxAttempts = 5;
|
|
816
|
-
// while (!id && attempts < maxAttempts) {
|
|
817
|
-
// attempts++;
|
|
818
|
-
// this.log(`🔍 Attempt ${attempts}/${maxAttempts} to get page ID`);
|
|
819
|
-
// id = await this.getPageId(page);
|
|
820
|
-
// if (!id) {
|
|
821
|
-
// this.log(`⏳ Page ID not found, waiting 500ms before retry...`);
|
|
822
|
-
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
823
|
-
// }
|
|
824
|
-
// }
|
|
825
|
-
// this.log("🔍 Retrieved page ID after retries", {
|
|
826
|
-
// id,
|
|
827
|
-
// url: page.url(),
|
|
828
|
-
// attempts,
|
|
829
|
-
// });
|
|
830
|
-
// if (id) {
|
|
831
|
-
// this.pages.set(id, page);
|
|
832
|
-
// this._selectedPageId = id;
|
|
833
|
-
// this.log("✅ Tab created successfully", {
|
|
834
|
-
// id,
|
|
835
|
-
// url: page.url(),
|
|
836
|
-
// totalPages: this.pages.size,
|
|
837
|
-
// allPageIds: Array.from(this.pages.keys()),
|
|
838
|
-
// selectedPageId: this._selectedPageId,
|
|
839
|
-
// });
|
|
840
|
-
// } else {
|
|
841
|
-
// this.log("❌ Failed to get page ID for new tab after all retries", {
|
|
842
|
-
// url: page.url(),
|
|
843
|
-
// attempts,
|
|
844
|
-
// });
|
|
845
|
-
// // Try to get debug data to see what's available
|
|
846
|
-
// const debugData = await this.getDebugURLs();
|
|
847
|
-
// this.log("🔍 Current CDP state after failed ID retrieval", {
|
|
848
|
-
// debugPages: debugData.map((p) => ({ id: p.id, url: p.url, type: p.type })),
|
|
849
|
-
// });
|
|
850
|
-
// }
|
|
851
|
-
// await this.syncState();
|
|
852
|
-
// } catch (error) {
|
|
853
|
-
// this.log("❌ Error creating tab", {
|
|
854
|
-
// url,
|
|
855
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
856
|
-
// stack: error instanceof Error ? error.stack : undefined,
|
|
857
|
-
// });
|
|
858
|
-
// throw error;
|
|
859
|
-
// }
|
|
860
|
-
// }
|
|
861
|
-
// async closeTab(pageId: string): Promise<void> {
|
|
862
|
-
// try {
|
|
863
|
-
// this.log("🗑️ Closing tab", { pageId });
|
|
864
|
-
// const page = this.pages.get(pageId);
|
|
865
|
-
// if (page) {
|
|
866
|
-
// this.log("✅ Found page to close", { pageId, url: page.url() });
|
|
867
|
-
// await page.close();
|
|
868
|
-
// this.log("✅ Page closed successfully", { pageId });
|
|
869
|
-
// } else {
|
|
870
|
-
// this.log("⚠️ Page not found", {
|
|
871
|
-
// pageId,
|
|
872
|
-
// availablePageIds: Array.from(this.pages.keys()),
|
|
873
|
-
// });
|
|
874
|
-
// }
|
|
875
|
-
// } catch (error) {
|
|
876
|
-
// this.log("❌ Error closing tab", {
|
|
877
|
-
// pageId,
|
|
878
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
879
|
-
// });
|
|
880
|
-
// throw error;
|
|
881
|
-
// }
|
|
882
|
-
// }
|
|
883
|
-
// async selectTab(pageId: string): Promise<void> {
|
|
884
|
-
// try {
|
|
885
|
-
// this.log("🎯 Selecting tab", { pageId });
|
|
886
|
-
// const page = this.pages.get(pageId);
|
|
887
|
-
// if (page) {
|
|
888
|
-
// this._selectedPageId = pageId;
|
|
889
|
-
// await page.bringToFront();
|
|
890
|
-
// this.log("✅ Tab selected successfully", {
|
|
891
|
-
// pageId,
|
|
892
|
-
// url: page.url(),
|
|
893
|
-
// selectedPageId: this._selectedPageId,
|
|
894
|
-
// });
|
|
895
|
-
// await this.syncState();
|
|
896
|
-
// } else {
|
|
897
|
-
// this.log("⚠️ Page not found for selection", {
|
|
898
|
-
// pageId,
|
|
899
|
-
// availablePageIds: Array.from(this.pages.keys()),
|
|
900
|
-
// });
|
|
901
|
-
// }
|
|
902
|
-
// } catch (error) {
|
|
903
|
-
// this.log("❌ Error selecting tab", {
|
|
904
|
-
// pageId,
|
|
905
|
-
// error: error instanceof Error ? error.message : String(error),
|
|
906
|
-
// });
|
|
907
|
-
// throw error;
|
|
908
|
-
// }
|
|
909
|
-
// }
|
|
910
|
-
// getSelectedPage(): Page | null {
|
|
911
|
-
// const page = this._selectedPageId ? this.pages.get(this._selectedPageId) || null : null;
|
|
912
|
-
// this.log("🔍 Getting selected page", {
|
|
913
|
-
// selectedPageId: this._selectedPageId,
|
|
914
|
-
// found: !!page,
|
|
915
|
-
// url: page?.url(),
|
|
916
|
-
// });
|
|
917
|
-
// return page;
|
|
918
|
-
// }
|
|
919
|
-
// }
|