@wordbricks/playwright-mcp 0.1.19 → 0.1.22

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.
Files changed (88) hide show
  1. package/README.md +54 -44
  2. package/cli-wrapper.js +15 -14
  3. package/cli.js +1 -1
  4. package/config.d.ts +11 -6
  5. package/index.d.ts +7 -5
  6. package/index.js +1 -1
  7. package/lib/browserContextFactory.js +131 -58
  8. package/lib/browserServerBackend.js +14 -12
  9. package/lib/config.js +60 -46
  10. package/lib/context.js +41 -39
  11. package/lib/extension/cdpRelay.js +67 -61
  12. package/lib/extension/extensionContextFactory.js +10 -10
  13. package/lib/frameworkPatterns.js +21 -21
  14. package/lib/hooks/antiBotDetectionHook.js +178 -0
  15. package/lib/hooks/core.js +11 -10
  16. package/lib/hooks/eventConsumer.js +29 -16
  17. package/lib/hooks/events.js +3 -3
  18. package/lib/hooks/formatToolCallEvent.js +3 -7
  19. package/lib/hooks/frameworkStateHook.js +40 -40
  20. package/lib/hooks/grouping.js +3 -3
  21. package/lib/hooks/jsonLdDetectionHook.js +44 -37
  22. package/lib/hooks/networkFilters.js +24 -15
  23. package/lib/hooks/networkSetup.js +11 -6
  24. package/lib/hooks/networkTrackingHook.js +31 -19
  25. package/lib/hooks/pageHeightHook.js +9 -9
  26. package/lib/hooks/registry.js +18 -16
  27. package/lib/hooks/requireTabHook.js +3 -3
  28. package/lib/hooks/schema.js +44 -32
  29. package/lib/hooks/waitHook.js +7 -7
  30. package/lib/index.js +12 -10
  31. package/lib/mcp/inProcessTransport.js +3 -4
  32. package/lib/mcp/proxyBackend.js +43 -28
  33. package/lib/mcp/server.js +24 -19
  34. package/lib/mcp/tool.js +14 -8
  35. package/lib/mcp/transport.js +60 -53
  36. package/lib/playwrightTransformer.js +129 -106
  37. package/lib/program.js +54 -52
  38. package/lib/response.js +36 -30
  39. package/lib/sessionLog.js +19 -17
  40. package/lib/tab.js +41 -39
  41. package/lib/tools/common.js +19 -19
  42. package/lib/tools/console.js +11 -11
  43. package/lib/tools/dialogs.js +18 -15
  44. package/lib/tools/evaluate.js +26 -17
  45. package/lib/tools/extractFrameworkState.js +48 -37
  46. package/lib/tools/files.js +17 -14
  47. package/lib/tools/form.js +32 -23
  48. package/lib/tools/getSnapshot.js +14 -15
  49. package/lib/tools/getVisibleHtml.js +33 -17
  50. package/lib/tools/install.js +20 -20
  51. package/lib/tools/keyboard.js +29 -24
  52. package/lib/tools/mouse.js +29 -31
  53. package/lib/tools/navigate.js +19 -23
  54. package/lib/tools/network.js +12 -14
  55. package/lib/tools/networkDetail.js +68 -61
  56. package/lib/tools/networkSearch/bodySearch.js +46 -32
  57. package/lib/tools/networkSearch/grouping.js +15 -6
  58. package/lib/tools/networkSearch/helpers.js +4 -4
  59. package/lib/tools/networkSearch/searchHtml.js +25 -16
  60. package/lib/tools/networkSearch/urlSearch.js +56 -14
  61. package/lib/tools/networkSearch.js +65 -35
  62. package/lib/tools/pdf.js +13 -12
  63. package/lib/tools/repl.js +66 -54
  64. package/lib/tools/screenshot.js +57 -33
  65. package/lib/tools/scroll.js +29 -24
  66. package/lib/tools/snapshot.js +66 -49
  67. package/lib/tools/tabs.js +22 -19
  68. package/lib/tools/tool.js +5 -3
  69. package/lib/tools/utils.js +17 -13
  70. package/lib/tools/wait.js +24 -19
  71. package/lib/tools.js +21 -20
  72. package/lib/utils/adBlockFilter.js +29 -26
  73. package/lib/utils/codegen.js +20 -16
  74. package/lib/utils/extensionPath.js +4 -4
  75. package/lib/utils/fileUtils.js +17 -13
  76. package/lib/utils/graphql.js +69 -58
  77. package/lib/utils/guid.js +3 -3
  78. package/lib/utils/httpServer.js +9 -9
  79. package/lib/utils/log.js +3 -3
  80. package/lib/utils/manualPromise.js +7 -7
  81. package/lib/utils/networkFormat.js +7 -5
  82. package/lib/utils/package.js +4 -4
  83. package/lib/utils/sanitizeHtml.js +66 -34
  84. package/lib/utils/truncate.js +25 -25
  85. package/lib/utils/withTimeout.js +1 -1
  86. package/package.json +34 -57
  87. package/src/index.ts +27 -17
  88. package/LICENSE +0 -202
@@ -1,18 +1,21 @@
1
- import { getEventsAfter, updateLastSeenId } from './events.js';
2
- import { formatWaitEvent } from './waitHook.js';
3
- import { formatPageHeightEvent } from './pageHeightHook.js';
4
- import { formatNetworkEvent } from './networkTrackingHook.js';
5
- import { planGroupedMessages } from './grouping.js';
6
- import { formatToolCallEvent } from './formatToolCallEvent.js';
7
- import { formatFrameworkStateEvent } from './frameworkStateHook.js';
8
- import { formatJsonLdEvent } from './jsonLdDetectionHook.js';
1
+ import { formatAntiBotEvent, getAntiBotProviderConfigs, } from "./antiBotDetectionHook.js";
2
+ import { getEventsAfter, isEventType, updateLastSeenId } from "./events.js";
3
+ import { formatToolCallEvent } from "./formatToolCallEvent.js";
4
+ import { formatFrameworkStateEvent } from "./frameworkStateHook.js";
5
+ import { planGroupedMessages } from "./grouping.js";
6
+ import { formatJsonLdEvent } from "./jsonLdDetectionHook.js";
7
+ import { isAntiBotUrl } from "./networkFilters.js";
8
+ import { formatNetworkEvent } from "./networkTrackingHook.js";
9
+ import { formatPageHeightEvent } from "./pageHeightHook.js";
10
+ import { formatWaitEvent } from "./waitHook.js";
9
11
  const eventFormatters = {
10
- 'wait': formatWaitEvent,
11
- 'page-height-change': formatPageHeightEvent,
12
- 'network-request': formatNetworkEvent,
13
- 'tool-call': formatToolCallEvent,
14
- 'framework-state': formatFrameworkStateEvent,
15
- 'json-ld': formatJsonLdEvent,
12
+ wait: formatWaitEvent,
13
+ "page-height-change": formatPageHeightEvent,
14
+ "network-request": formatNetworkEvent,
15
+ "tool-call": formatToolCallEvent,
16
+ "framework-state": formatFrameworkStateEvent,
17
+ "json-ld": formatJsonLdEvent,
18
+ "anti-bot": formatAntiBotEvent,
16
19
  };
17
20
  const formatEvent = (event) => {
18
21
  const formatter = eventFormatters[event.type];
@@ -25,13 +28,23 @@ const consumeEvent = (event, response, plan) => {
25
28
  const formattedMessage = replacement ?? formatEvent(event);
26
29
  response.addEvent(`[${event.id}] ${formattedMessage}`);
27
30
  };
31
+ const shouldHideEvent = (event) => {
32
+ const isNetworkRequest = isEventType("network-request");
33
+ if (!isNetworkRequest(event))
34
+ return false;
35
+ if (isAntiBotUrl(event.data.url))
36
+ return true;
37
+ const configs = getAntiBotProviderConfigs().filter((config) => config.provider === "cloudflare-turnstile");
38
+ return configs.some((config) => config.match(event));
39
+ };
28
40
  export const consumeEvents = (context, eventStore, response) => {
29
41
  const unconsumedEvents = getEventsAfter(eventStore, eventStore.lastSeenEventId);
30
42
  if (unconsumedEvents.length === 0)
31
43
  return;
32
- const plan = planGroupedMessages(unconsumedEvents);
44
+ const visibleEvents = unconsumedEvents.filter((event) => !shouldHideEvent(event));
45
+ const plan = planGroupedMessages(visibleEvents);
33
46
  // Consume all events in chronological order
34
- for (const event of unconsumedEvents)
47
+ for (const event of visibleEvents)
35
48
  consumeEvent(event, response, plan);
36
49
  // Update last seen event ID
37
50
  const latestEvent = unconsumedEvents[unconsumedEvents.length - 1];
@@ -1,4 +1,4 @@
1
- import { pipe, filter, toArray } from '@fxts/core';
1
+ import { filter, pipe, toArray } from "@fxts/core";
2
2
  export const isEventType = (type) => (event) => event.type === type;
3
3
  export const createEventStore = () => ({
4
4
  events: new Map(),
@@ -12,7 +12,7 @@ export const trackEvent = (context, params) => {
12
12
  id: eventId,
13
13
  type: params.type,
14
14
  data: params.data,
15
- timestamp: params.timestamp ?? Date.now()
15
+ timestamp: params.timestamp ?? Date.now(),
16
16
  };
17
17
  store.events.set(eventId, event);
18
18
  return eventId;
@@ -26,7 +26,7 @@ export const getEventsAfter = (store, afterEventId) => {
26
26
  if (!afterEventId) {
27
27
  return pipe(store.events.values(), toArray);
28
28
  }
29
- return pipe(store.events.values(), filter(event => event.id > afterEventId), toArray);
29
+ return pipe(store.events.values(), filter((event) => event.id > afterEventId), toArray);
30
30
  };
31
31
  const eventStoreMap = new WeakMap();
32
32
  export const getEventStore = (context) => {
@@ -3,14 +3,10 @@ export const formatToolCallEvent = (event) => {
3
3
  // Format parameters (truncate if too long)
4
4
  const paramStr = params && Object.keys(params).length > 0
5
5
  ? ` with params: ${JSON.stringify(params, null, 0).slice(0, 100)}`
6
- : '';
6
+ : "";
7
7
  // Format execution time if available
8
- const timeStr = executionTime !== undefined
9
- ? ` (${executionTime}ms)`
10
- : '';
8
+ const timeStr = executionTime !== undefined ? ` (${executionTime}ms)` : "";
11
9
  // Format success status if available
12
- const statusStr = success !== undefined
13
- ? success ? ' ✓' : ' ✗'
14
- : '';
10
+ const statusStr = success !== undefined ? (success ? " ✓" : " ✗") : "";
15
11
  return `Tool ${toolName}${paramStr}${timeStr}${statusStr}`;
16
12
  };
@@ -1,7 +1,7 @@
1
- import { Ok } from '../utils/result.js';
2
- import { hookNameSchema } from './schema.js';
3
- import { trackEvent } from './events.js';
4
- import { FRAMEWORK_STATE_PATTERNS, MAX_DISPLAY_ITEMS } from '../frameworkPatterns.js';
1
+ import { FRAMEWORK_STATE_PATTERNS, MAX_DISPLAY_ITEMS, } from "../frameworkPatterns.js";
2
+ import { Ok } from "../utils/result.js";
3
+ import { trackEvent } from "./events.js";
4
+ import { hookNameSchema } from "./schema.js";
5
5
  const pageFrameworkStates = new WeakMap();
6
6
  const seenFrameworkKeysByContext = new WeakMap();
7
7
  const getSeenFrameworkKeys = (context) => {
@@ -13,7 +13,7 @@ const getSeenFrameworkKeys = (context) => {
13
13
  return set;
14
14
  };
15
15
  export const frameworkStatePreHook = {
16
- name: hookNameSchema.enum['framework-state-pre'],
16
+ name: hookNameSchema.enum["framework-state-pre"],
17
17
  handler: async (context) => {
18
18
  const frameworkState = await detectFrameworkState(context);
19
19
  if (frameworkState) {
@@ -21,27 +21,29 @@ export const frameworkStatePreHook = {
21
21
  if (context.tab?.page)
22
22
  pageFrameworkStates.set(context.tab.page, frameworkState);
23
23
  // Track event for newly detected framework state
24
- const newKeys = Object.keys(frameworkState).filter(key => !getSeenFrameworkKeys(context.context).has(key));
24
+ const newKeys = Object.keys(frameworkState).filter((key) => !getSeenFrameworkKeys(context.context).has(key));
25
25
  if (newKeys.length > 0) {
26
26
  trackEvent(context.context, {
27
- type: 'framework-state',
27
+ type: "framework-state",
28
28
  data: {
29
29
  state: frameworkState,
30
- action: 'detected',
30
+ action: "detected",
31
31
  },
32
32
  });
33
33
  // Mark keys as seen
34
- newKeys.forEach(key => getSeenFrameworkKeys(context.context).add(key));
34
+ newKeys.forEach((key) => getSeenFrameworkKeys(context.context).add(key));
35
35
  }
36
36
  }
37
37
  return Ok(undefined);
38
38
  },
39
39
  };
40
40
  export const frameworkStatePostHook = {
41
- name: hookNameSchema.enum['framework-state-post'],
41
+ name: hookNameSchema.enum["framework-state-post"],
42
42
  handler: async (context) => {
43
43
  const newFrameworkState = await detectFrameworkState(context);
44
- const initialState = context.tab?.page ? pageFrameworkStates.get(context.tab.page) : undefined;
44
+ const initialState = context.tab?.page
45
+ ? pageFrameworkStates.get(context.tab.page)
46
+ : undefined;
45
47
  if (newFrameworkState) {
46
48
  const changes = [];
47
49
  if (initialState) {
@@ -64,27 +66,27 @@ export const frameworkStatePostHook = {
64
66
  }
65
67
  if (changes.length > 0) {
66
68
  trackEvent(context.context, {
67
- type: 'framework-state',
69
+ type: "framework-state",
68
70
  data: {
69
71
  state: newFrameworkState,
70
72
  changes,
71
- action: 'changed',
73
+ action: "changed",
72
74
  },
73
75
  });
74
76
  }
75
77
  }
76
78
  else {
77
79
  // No initial state, but we have state now
78
- const newKeys = Object.keys(newFrameworkState).filter(key => !getSeenFrameworkKeys(context.context).has(key));
80
+ const newKeys = Object.keys(newFrameworkState).filter((key) => !getSeenFrameworkKeys(context.context).has(key));
79
81
  if (newKeys.length > 0) {
80
82
  trackEvent(context.context, {
81
- type: 'framework-state',
83
+ type: "framework-state",
82
84
  data: {
83
85
  state: newFrameworkState,
84
- action: 'detected',
86
+ action: "detected",
85
87
  },
86
88
  });
87
- newKeys.forEach(key => getSeenFrameworkKeys(context.context).add(key));
89
+ newKeys.forEach((key) => getSeenFrameworkKeys(context.context).add(key));
88
90
  }
89
91
  }
90
92
  // Update stored state
@@ -107,11 +109,11 @@ async function detectFrameworkState(context) {
107
109
  const value = window[pattern];
108
110
  // Only capture if it's a non-empty object or has meaningful content
109
111
  if (value &&
110
- (typeof value === 'object' || typeof value === 'string')) {
112
+ (typeof value === "object" || typeof value === "string")) {
111
113
  state[pattern] =
112
- typeof value === 'object'
114
+ typeof value === "object"
113
115
  ? {
114
- type: 'object',
116
+ type: "object",
115
117
  keys: Object.keys(value).slice(0, MAX_ITEMS * 2),
116
118
  }
117
119
  : {
@@ -127,19 +129,19 @@ async function detectFrameworkState(context) {
127
129
  }
128
130
  // Also check for React Fiber internals
129
131
  const reactRootSelectors = [
130
- '#__next',
131
- '#root',
132
- '#app',
133
- '[data-reactroot]',
132
+ "#__next",
133
+ "#root",
134
+ "#app",
135
+ "[data-reactroot]",
134
136
  ];
135
137
  for (const selector of reactRootSelectors) {
136
138
  const element = document.querySelector(selector);
137
139
  if (element) {
138
- const fiberKey = Object.keys(element).find(key => key.startsWith('__reactInternalInstance') ||
139
- key.startsWith('__reactFiber') ||
140
- key.startsWith('_reactRootContainer'));
140
+ const fiberKey = Object.keys(element).find((key) => key.startsWith("__reactInternalInstance") ||
141
+ key.startsWith("__reactFiber") ||
142
+ key.startsWith("_reactRootContainer"));
141
143
  if (fiberKey) {
142
- state['React Fiber Root'] = { selector, fiberKey };
144
+ state["React Fiber Root"] = { selector, fiberKey };
143
145
  break;
144
146
  }
145
147
  }
@@ -149,32 +151,30 @@ async function detectFrameworkState(context) {
149
151
  return result;
150
152
  }
151
153
  function formatValue(value) {
152
- if (typeof value === 'object' && value !== null && 'type' in value) {
153
- if (value.type === 'object' &&
154
- 'keys' in value &&
155
- Array.isArray(value.keys))
156
- return `{${value.keys.join(', ')}${value.keys.length >= MAX_DISPLAY_ITEMS * 2 ? ', ...' : ''}}`;
157
- else if ('preview' in value && typeof value.preview === 'string')
158
- return `"${value.preview}${value.preview.length >= 200 ? '...' : ''}"`;
154
+ if (typeof value === "object" && value !== null && "type" in value) {
155
+ if (value.type === "object" && "keys" in value && Array.isArray(value.keys))
156
+ return `{${value.keys.join(", ")}${value.keys.length >= MAX_DISPLAY_ITEMS * 2 ? ", ..." : ""}}`;
157
+ else if ("preview" in value && typeof value.preview === "string")
158
+ return `"${value.preview}${value.preview.length >= 200 ? "..." : ""}"`;
159
159
  }
160
160
  return JSON.stringify(value);
161
161
  }
162
162
  export const formatFrameworkStateEvent = (event) => {
163
163
  const { state, changes, action } = event.data;
164
164
  const messages = [];
165
- if (action === 'detected') {
166
- messages.push('Framework state detected:');
165
+ if (action === "detected") {
166
+ messages.push("Framework state detected:");
167
167
  const keys = Object.keys(state);
168
168
  for (const key of keys) {
169
169
  const value = state[key];
170
170
  messages.push(` ${key}: ${formatValue(value)}`);
171
171
  }
172
172
  }
173
- else if (action === 'changed' && changes) {
174
- messages.push('Framework state changed:');
173
+ else if (action === "changed" && changes) {
174
+ messages.push("Framework state changed:");
175
175
  messages.push(...changes.map((change) => ` ${change}`));
176
176
  }
177
- return messages.join('\n');
177
+ return messages.join("\n");
178
178
  };
179
179
  export const frameworkStateHooks = {
180
180
  pre: frameworkStatePreHook,
@@ -7,7 +7,7 @@ export const defineGroupingRule = (spec) => {
7
7
  keyOf: (e) => {
8
8
  // Planner guarantees keyOf is only called when match(e) is true
9
9
  if (!match(e))
10
- return '';
10
+ return "";
11
11
  return spec.keyOf(e);
12
12
  },
13
13
  summaryOf: (first, run) => {
@@ -19,9 +19,9 @@ export const defineGroupingRule = (spec) => {
19
19
  }
20
20
  const typedFirst = match(first) ? first : typedRun[0];
21
21
  if (!typedFirst)
22
- return '';
22
+ return "";
23
23
  return spec.summaryOf(typedFirst, typedRun);
24
- }
24
+ },
25
25
  };
26
26
  };
27
27
  export const registerGroupingRule = (type, rule) => {
@@ -1,6 +1,6 @@
1
- import { Ok } from '../utils/result.js';
2
- import { hookNameSchema } from './schema.js';
3
- import { trackEvent } from './events.js';
1
+ import { Ok } from "../utils/result.js";
2
+ import { trackEvent } from "./events.js";
3
+ import { hookNameSchema } from "./schema.js";
4
4
  const MAX_DISPLAY_ITEMS = 5;
5
5
  const pageJsonLdStates = new WeakMap();
6
6
  const seenJsonLdTypesByContext = new WeakMap();
@@ -13,7 +13,7 @@ const getSeenJsonLdTypes = (context) => {
13
13
  return set;
14
14
  };
15
15
  export const jsonLdDetectionPreHook = {
16
- name: hookNameSchema.enum['json-ld-detection-pre'],
16
+ name: hookNameSchema.enum["json-ld-detection-pre"],
17
17
  handler: async (context) => {
18
18
  const jsonLdState = await detectJsonLdState(context);
19
19
  if (jsonLdState) {
@@ -21,27 +21,29 @@ export const jsonLdDetectionPreHook = {
21
21
  if (context.tab?.page)
22
22
  pageJsonLdStates.set(context.tab.page, jsonLdState);
23
23
  // Track event for newly detected JSON-LD types
24
- const newTypes = Object.keys(jsonLdState).filter(type => !getSeenJsonLdTypes(context.context).has(type));
24
+ const newTypes = Object.keys(jsonLdState).filter((type) => !getSeenJsonLdTypes(context.context).has(type));
25
25
  if (newTypes.length > 0) {
26
26
  trackEvent(context.context, {
27
- type: 'json-ld',
27
+ type: "json-ld",
28
28
  data: {
29
29
  state: jsonLdState,
30
- action: 'detected',
30
+ action: "detected",
31
31
  },
32
32
  });
33
33
  // Mark types as seen
34
- newTypes.forEach(type => getSeenJsonLdTypes(context.context).add(type));
34
+ newTypes.forEach((type) => getSeenJsonLdTypes(context.context).add(type));
35
35
  }
36
36
  }
37
37
  return Ok(undefined);
38
38
  },
39
39
  };
40
40
  export const jsonLdDetectionPostHook = {
41
- name: hookNameSchema.enum['json-ld-detection-post'],
41
+ name: hookNameSchema.enum["json-ld-detection-post"],
42
42
  handler: async (context) => {
43
43
  const newJsonLdState = await detectJsonLdState(context);
44
- const initialState = context.tab?.page ? pageJsonLdStates.get(context.tab.page) : undefined;
44
+ const initialState = context.tab?.page
45
+ ? pageJsonLdStates.get(context.tab.page)
46
+ : undefined;
45
47
  if (newJsonLdState || initialState) {
46
48
  const changes = [];
47
49
  if (initialState && newJsonLdState) {
@@ -54,37 +56,35 @@ export const jsonLdDetectionPostHook = {
54
56
  const initInfo = initialState[type];
55
57
  const currInfo = newJsonLdState[type];
56
58
  if (!initInfo && currInfo)
57
- changes.push(`+ ${type}${currInfo.count > 1 ? ` (${currInfo.count} instances)` : ''}`);
59
+ changes.push(`+ ${type}${currInfo.count > 1 ? ` (${currInfo.count} instances)` : ""}`);
58
60
  else if (initInfo && !currInfo)
59
- changes.push(`- ${type}${initInfo.count > 1 ? ` (${initInfo.count} instances)` : ''}`);
60
- else if (initInfo &&
61
- currInfo &&
62
- initInfo.count !== currInfo.count)
61
+ changes.push(`- ${type}${initInfo.count > 1 ? ` (${initInfo.count} instances)` : ""}`);
62
+ else if (initInfo && currInfo && initInfo.count !== currInfo.count)
63
63
  changes.push(`~ ${type}: ${initInfo.count} → ${currInfo.count} instances`);
64
64
  }
65
65
  if (changes.length > 0) {
66
66
  trackEvent(context.context, {
67
- type: 'json-ld',
67
+ type: "json-ld",
68
68
  data: {
69
69
  state: newJsonLdState,
70
70
  changes,
71
- action: 'changed',
71
+ action: "changed",
72
72
  },
73
73
  });
74
74
  }
75
75
  }
76
76
  else if (newJsonLdState && !initialState) {
77
77
  // No initial state, but we have state now
78
- const newTypes = Object.keys(newJsonLdState).filter(type => !getSeenJsonLdTypes(context.context).has(type));
78
+ const newTypes = Object.keys(newJsonLdState).filter((type) => !getSeenJsonLdTypes(context.context).has(type));
79
79
  if (newTypes.length > 0) {
80
80
  trackEvent(context.context, {
81
- type: 'json-ld',
81
+ type: "json-ld",
82
82
  data: {
83
83
  state: newJsonLdState,
84
- action: 'detected',
84
+ action: "detected",
85
85
  },
86
86
  });
87
- newTypes.forEach(type => getSeenJsonLdTypes(context.context).add(type));
87
+ newTypes.forEach((type) => getSeenJsonLdTypes(context.context).add(type));
88
88
  }
89
89
  }
90
90
  // Update stored state
@@ -104,23 +104,27 @@ async function detectJsonLdState(context) {
104
104
  scripts.forEach((script, index) => {
105
105
  try {
106
106
  // Parse JSON
107
- const data = JSON.parse(script.textContent || '{}');
107
+ const data = JSON.parse(script.textContent || "{}");
108
108
  // Extract @type - handle both single and array types
109
109
  let types = [];
110
- if (data['@type']) {
111
- types = Array.isArray(data['@type']) ? data['@type'] : [data['@type']];
110
+ if (data["@type"]) {
111
+ types = Array.isArray(data["@type"])
112
+ ? data["@type"]
113
+ : [data["@type"]];
112
114
  }
113
- else if (data['@graph'] && Array.isArray(data['@graph'])) {
115
+ else if (data["@graph"] && Array.isArray(data["@graph"])) {
114
116
  // Handle @graph structures
115
- data['@graph'].forEach((item) => {
116
- if (item['@type']) {
117
- const itemTypes = Array.isArray(item['@type']) ? item['@type'] : [item['@type']];
117
+ data["@graph"].forEach((item) => {
118
+ if (item["@type"]) {
119
+ const itemTypes = Array.isArray(item["@type"])
120
+ ? item["@type"]
121
+ : [item["@type"]];
118
122
  types.push(...itemTypes);
119
123
  }
120
124
  });
121
125
  }
122
126
  // Count occurrences of each type
123
- types.forEach(type => {
127
+ types.forEach((type) => {
124
128
  if (!state[type])
125
129
  state[type] = { count: 0, indices: [] };
126
130
  state[type].count++;
@@ -128,9 +132,12 @@ async function detectJsonLdState(context) {
128
132
  });
129
133
  }
130
134
  catch (e) {
131
- state['InvalidJSON-LD'] = state['InvalidJSON-LD'] || { count: 0, indices: [] };
132
- state['InvalidJSON-LD'].count++;
133
- state['InvalidJSON-LD'].indices.push(index);
135
+ state["InvalidJSON-LD"] = state["InvalidJSON-LD"] || {
136
+ count: 0,
137
+ indices: [],
138
+ };
139
+ state["InvalidJSON-LD"].count++;
140
+ state["InvalidJSON-LD"].indices.push(index);
134
141
  }
135
142
  });
136
143
  return Object.keys(state).length > 0 ? state : null;
@@ -159,15 +166,15 @@ function buildStateMessages(state, types) {
159
166
  export const formatJsonLdEvent = (event) => {
160
167
  const { state, changes, action } = event.data;
161
168
  const messages = [];
162
- if (action === 'detected') {
163
- messages.push('New JSON-LD types detected:');
169
+ if (action === "detected") {
170
+ messages.push("New JSON-LD types detected:");
164
171
  messages.push(...buildStateMessages(state));
165
172
  }
166
- else if (action === 'changed' && changes) {
167
- messages.push('JSON-LD changes after action:');
173
+ else if (action === "changed" && changes) {
174
+ messages.push("JSON-LD changes after action:");
168
175
  messages.push(...changes.map((change) => ` ${change}`));
169
176
  }
170
- return messages.join('\n');
177
+ return messages.join("\n");
171
178
  };
172
179
  export const jsonLdDetectionHooks = {
173
180
  pre: jsonLdDetectionPreHook,
@@ -1,16 +1,23 @@
1
- const MEANINGFUL_RESOURCE_TYPES = ['document', 'xhr', 'fetch'];
2
- const ALLOWED_METHODS = ['GET', 'POST'];
1
+ const MEANINGFUL_RESOURCE_TYPES = ["document", "xhr", "fetch"];
2
+ const ALLOWED_METHODS = ["GET", "POST"];
3
3
  const EXCLUDED_EXTENSIONS = [
4
- '.svg',
5
- '.css',
6
- '.map', // JS files and source maps
4
+ ".svg",
5
+ ".css",
6
+ ".map", // JS files and source maps
7
7
  ];
8
8
  const hasExcludedExtension = (url) => {
9
- return EXCLUDED_EXTENSIONS.some(ext => {
10
- const extRegex = new RegExp(`${ext.replace('.', '\\.')}(\\?|#|$)`, 'i');
9
+ return EXCLUDED_EXTENSIONS.some((ext) => {
10
+ const extRegex = new RegExp(`${ext.replace(".", "\\.")}(\\?|#|$)`, "i");
11
11
  return extRegex.test(url);
12
12
  });
13
13
  };
14
+ export const isAntiBotUrl = (url) => {
15
+ if (url.includes("challenges.cloudflare.com"))
16
+ return true;
17
+ if (url.includes(".awswaf.com"))
18
+ return true;
19
+ return false;
20
+ };
14
21
  const isSuccessfulStatus = (status) => {
15
22
  // Status 0 is for failed requests, which we want to capture
16
23
  // 2xx status codes are successful
@@ -18,10 +25,12 @@ const isSuccessfulStatus = (status) => {
18
25
  return status === 0 || (status >= 200 && status < 300 && status !== 204);
19
26
  };
20
27
  export const shouldCaptureRequest = (method, url, status, resourceType) => {
21
- return !hasExcludedExtension(url) &&
28
+ if (isAntiBotUrl(url))
29
+ return true;
30
+ return (!hasExcludedExtension(url) &&
22
31
  MEANINGFUL_RESOURCE_TYPES.includes(resourceType) &&
23
32
  ALLOWED_METHODS.includes(method) &&
24
- isSuccessfulStatus(status);
33
+ isSuccessfulStatus(status));
25
34
  };
26
35
  /**
27
36
  * Format URL with trimmed parameters
@@ -34,7 +43,7 @@ export const formatUrlWithTrimmedParams = (url) => {
34
43
  const trimmedParams = new URLSearchParams();
35
44
  params.forEach((value, key) => {
36
45
  if (value.length > 5)
37
- trimmedParams.set(key, value.substring(0, 5) + '...');
46
+ trimmedParams.set(key, value.substring(0, 5) + "...");
38
47
  else
39
48
  trimmedParams.set(key, value);
40
49
  });
@@ -52,10 +61,10 @@ export const formatUrlWithTrimmedParams = (url) => {
52
61
  */
53
62
  export const normalizePathname = (pathname) => {
54
63
  if (!pathname)
55
- return '/';
56
- if (pathname === '/')
57
- return '/';
58
- return pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
64
+ return "/";
65
+ if (pathname === "/")
66
+ return "/";
67
+ return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
59
68
  };
60
69
  /**
61
70
  * Normalize URL for grouping by ignoring query/hash and trailing slash
@@ -67,7 +76,7 @@ export const normalizeUrlForGrouping = (url) => {
67
76
  }
68
77
  catch {
69
78
  // Fallback for non-standard/relative URLs: strip query/hash and trailing slash
70
- const base = url.split(/[?#]/)[0] || '/';
79
+ const base = url.split(/[?#]/)[0] || "/";
71
80
  return normalizePathname(base);
72
81
  }
73
82
  };
@@ -1,5 +1,5 @@
1
- import { trackEvent } from './events.js';
2
- import { shouldCaptureRequest } from './networkFilters.js';
1
+ import { trackEvent } from "./events.js";
2
+ import { shouldCaptureRequest } from "./networkFilters.js";
3
3
  const eventIdToEntryMap = new WeakMap();
4
4
  const getEventIdMap = (context) => {
5
5
  let map = eventIdToEntryMap.get(context);
@@ -11,7 +11,7 @@ const getEventIdMap = (context) => {
11
11
  };
12
12
  export const getNetworkEventEntry = (context, id) => getEventIdMap(context).get(id);
13
13
  export const setupNetworkTracking = (context, page) => {
14
- page.on('response', response => {
14
+ page.on("response", async (response) => {
15
15
  const request = response.request();
16
16
  const method = request.method();
17
17
  const url = request.url();
@@ -19,21 +19,26 @@ export const setupNetworkTracking = (context, page) => {
19
19
  const resourceType = request.resourceType();
20
20
  // Apply filters before saving the event
21
21
  if (shouldCaptureRequest(method, url, status, resourceType)) {
22
+ const setCookies = await response
23
+ .headerValues("set-cookie")
24
+ .catch(() => []);
25
+ const cookieValues = setCookies.length ? setCookies : undefined;
22
26
  const networkData = {
23
27
  method,
24
28
  url,
25
29
  status,
26
30
  resourceType,
27
31
  postData: request.postData() || undefined,
32
+ setCookies: cookieValues,
28
33
  };
29
34
  const id = trackEvent(context, {
30
- type: 'network-request',
35
+ type: "network-request",
31
36
  data: networkData,
32
37
  });
33
38
  getEventIdMap(context).set(id, { request, response });
34
39
  }
35
40
  });
36
- page.on('requestfailed', request => {
41
+ page.on("requestfailed", (request) => {
37
42
  const method = request.method();
38
43
  const url = request.url();
39
44
  const status = 0; // Failed requests have status 0
@@ -47,7 +52,7 @@ export const setupNetworkTracking = (context, page) => {
47
52
  postData: request.postData() || undefined,
48
53
  };
49
54
  const id = trackEvent(context, {
50
- type: 'network-request',
55
+ type: "network-request",
51
56
  data: networkData,
52
57
  });
53
58
  getEventIdMap(context).set(id, { request });