browser-debugger-cli 0.11.0 → 0.12.0

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 (98) hide show
  1. package/README.md +142 -79
  2. package/dist/commands/css.d.ts +13 -0
  3. package/dist/commands/css.js +53 -0
  4. package/dist/commands/dom/audit.d.ts +14 -0
  5. package/dist/commands/dom/audit.js +87 -0
  6. package/dist/commands/dom/formInteraction.js +36 -6
  7. package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
  8. package/dist/commands/dom/helpers/keyAttributes.js +6 -4
  9. package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
  10. package/dist/commands/dom/helpers/screenshot.js +158 -38
  11. package/dist/commands/dom/index.js +4 -1
  12. package/dist/commands/dom/screenshot.js +10 -6
  13. package/dist/commands/dom/wait.js +5 -3
  14. package/dist/commands/helpJson.js +1 -1
  15. package/dist/commands/optionBehaviors.js +21 -6
  16. package/dist/commands/page.js +7 -4
  17. package/dist/commands/peek.d.ts +7 -0
  18. package/dist/commands/peek.js +65 -23
  19. package/dist/commands/shared/optionTypes.d.ts +5 -1
  20. package/dist/commands/start.d.ts +13 -0
  21. package/dist/commands/start.js +19 -2
  22. package/dist/commands/tail.d.ts +7 -1
  23. package/dist/commands/tail.js +13 -62
  24. package/dist/commands.js +2 -0
  25. package/dist/daemon/session/commandRegistry.js +7 -1
  26. package/dist/daemon/session/plugins.js +4 -52
  27. package/dist/daemon.js +7986 -6848
  28. package/dist/errors/messages.d.ts +34 -4
  29. package/dist/errors/messages.js +68 -5
  30. package/dist/index.js +610 -186
  31. package/dist/ipc/client.d.ts +4 -0
  32. package/dist/ipc/client.js +8 -0
  33. package/dist/ipc/protocol/auditTypes.d.ts +129 -0
  34. package/dist/ipc/protocol/auditTypes.js +6 -0
  35. package/dist/ipc/protocol/commands.d.ts +23 -0
  36. package/dist/ipc/protocol/commands.js +2 -0
  37. package/dist/ipc/protocol/domTypes.d.ts +4 -0
  38. package/dist/ipc/protocol/inspectTypes.d.ts +71 -8
  39. package/dist/runtime/css/search.d.ts +39 -0
  40. package/dist/runtime/css/search.js +122 -0
  41. package/dist/runtime/dom/actionEffects.d.ts +4 -1
  42. package/dist/runtime/dom/actionEffects.js +8 -4
  43. package/dist/runtime/dom/audit.d.ts +19 -0
  44. package/dist/runtime/dom/audit.js +36 -0
  45. package/dist/runtime/dom/auditModel.d.ts +45 -0
  46. package/dist/runtime/dom/auditModel.js +215 -0
  47. package/dist/runtime/dom/auditScripts.d.ts +107 -0
  48. package/dist/runtime/dom/auditScripts.js +112 -0
  49. package/dist/runtime/dom/elementGeometry.d.ts +8 -2
  50. package/dist/runtime/dom/elementGeometry.js +24 -8
  51. package/dist/runtime/dom/elementInfo.d.ts +3 -2
  52. package/dist/runtime/dom/elementInfo.js +8 -2
  53. package/dist/runtime/dom/formFillHelpers/fill.js +2 -2
  54. package/dist/runtime/dom/inspect.d.ts +7 -0
  55. package/dist/runtime/dom/inspect.js +88 -23
  56. package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
  57. package/dist/runtime/dom/inspectAllStyles.js +89 -7
  58. package/dist/runtime/dom/inspectCascade.d.ts +19 -2
  59. package/dist/runtime/dom/inspectCascade.js +214 -44
  60. package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
  61. package/dist/runtime/dom/inspectCascadeModel.js +108 -34
  62. package/dist/runtime/dom/inspectHints.d.ts +26 -3
  63. package/dist/runtime/dom/inspectHints.js +125 -9
  64. package/dist/runtime/dom/inspectModel.d.ts +3 -0
  65. package/dist/runtime/dom/inspectModel.js +30 -7
  66. package/dist/runtime/dom/inspectPaintModel.d.ts +48 -22
  67. package/dist/runtime/dom/inspectPaintModel.js +180 -68
  68. package/dist/runtime/dom/inspectRules.d.ts +19 -0
  69. package/dist/runtime/dom/inspectRules.js +21 -5
  70. package/dist/runtime/dom/inspectScripts.d.ts +85 -12
  71. package/dist/runtime/dom/inspectScripts.js +314 -28
  72. package/dist/runtime/dom/inspectTree.js +10 -2
  73. package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
  74. package/dist/runtime/dom/inspectWhyModel.js +52 -10
  75. package/dist/runtime/dom/layout.js +31 -9
  76. package/dist/runtime/dom/reactEventHelpers.d.ts +7 -0
  77. package/dist/runtime/dom/reactEventHelpers.js +27 -9
  78. package/dist/runtime/page/emulation.d.ts +13 -4
  79. package/dist/runtime/page/emulation.js +69 -4
  80. package/dist/runtime/page/userAgent.d.ts +17 -0
  81. package/dist/runtime/page/userAgent.js +57 -0
  82. package/dist/types.d.ts +4 -0
  83. package/dist/ui/formatters/audit.d.ts +19 -0
  84. package/dist/ui/formatters/audit.js +106 -0
  85. package/dist/ui/formatters/dom.d.ts +1 -1
  86. package/dist/ui/formatters/dom.js +6 -3
  87. package/dist/ui/formatters/inspect.js +42 -15
  88. package/dist/ui/formatters/status.js +1 -1
  89. package/dist/ui/messages/commands.d.ts +44 -7
  90. package/dist/ui/messages/commands.js +83 -11
  91. package/dist/ui/messages/preview.d.ts +6 -0
  92. package/dist/ui/messages/preview.js +9 -1
  93. package/dist/utils/cssValues.js +36 -4
  94. package/dist/utils/decisionTrees.js +0 -5
  95. package/dist/utils/suggestions.d.ts +4 -2
  96. package/dist/utils/suggestions.js +7 -5
  97. package/dist/utils/taskMappings.js +1 -1
  98. package/package.json +3 -2
@@ -8,9 +8,12 @@ import { fetchPreviewOutput, createErrorResult, } from './shared/dataFetcher.js'
8
8
  import { followFetchFailure, setupFollowMode, } from './shared/followMode.js';
9
9
  import { handleValidationError } from './shared/handleValidationError.js';
10
10
  import { MAX_LAST_ITEMS, positiveIntRule, resourceTypeRule } from './shared/validation.js';
11
+ import { CommandError } from '../errors/index.js';
12
+ import { intervalWithoutFollowError } from '../errors/messages.js';
11
13
  import { filterByResourceType } from '../telemetry/filters.js';
12
14
  import { buildPreviewJsonData, formatPreview, } from '../ui/formatters/preview.js';
13
15
  import { followingPreviewMessage, stoppedFollowingPreviewMessage } from '../ui/messages/preview.js';
16
+ import { EXIT_CODES } from '../utils/exitCodes.js';
14
17
  function parseOptions(options) {
15
18
  const lastN = positiveIntRule({
16
19
  name: '--last',
@@ -20,7 +23,17 @@ function parseOptions(options) {
20
23
  allowZeroForAll: true,
21
24
  }).validate(options.last);
22
25
  const resourceTypes = resourceTypeRule().validate(options.type);
23
- return { lastN, resourceTypes };
26
+ if (options.interval !== undefined && !options.follow) {
27
+ const err = intervalWithoutFollowError();
28
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
29
+ }
30
+ const interval = positiveIntRule({
31
+ name: '--interval',
32
+ min: 100,
33
+ max: 60000,
34
+ default: 1000,
35
+ }).validate(options.interval);
36
+ return { lastN, resourceTypes, interval };
24
37
  }
25
38
  /**
26
39
  * Data section selected by `--network` / `--console`, if any.
@@ -75,11 +88,12 @@ function createPreviewOptions(base, resourceTypes, unfilteredCount) {
75
88
  }
76
89
  return options;
77
90
  }
78
- async function runFollowMode(options, lastN, resourceTypes, baseOptions) {
91
+ async function runFollowMode(options, parsed, baseOptions) {
92
+ const { lastN, resourceTypes, interval } = parsed;
79
93
  const showPreview = async () => {
80
94
  const result = await fetchAndFilterPreview(lastN, resourceTypes, peekSection(options));
81
95
  if (!result.success) {
82
- return followFetchFailure(result, { json: options.json, retryIntervalMs: 1000 });
96
+ return followFetchFailure(result, { json: options.json, retryIntervalMs: interval });
83
97
  }
84
98
  noteFollowConnected();
85
99
  if (!options.json)
@@ -91,9 +105,51 @@ async function runFollowMode(options, lastN, resourceTypes, baseOptions) {
91
105
  await setupFollowMode(showPreview, {
92
106
  startMessage: followingPreviewMessage,
93
107
  stopMessage: stoppedFollowingPreviewMessage,
94
- intervalMs: 1000,
108
+ intervalMs: interval,
95
109
  });
96
110
  }
111
+ /**
112
+ * Parse the options, reporting an invalid one and exiting.
113
+ *
114
+ * @param options - Peek options
115
+ * @returns Parsed options
116
+ */
117
+ function parsePeekOptions(options) {
118
+ try {
119
+ return parseOptions(options);
120
+ }
121
+ catch (error) {
122
+ handleValidationError(error, options.json ?? false);
123
+ }
124
+ }
125
+ /**
126
+ * How the preview is shown.
127
+ *
128
+ * @param options - Peek options
129
+ * @param lastN - Items to show
130
+ * @returns Preview options
131
+ */
132
+ function previewDisplayOptions(options, lastN) {
133
+ return {
134
+ json: options.json,
135
+ network: options.network,
136
+ console: options.console,
137
+ last: lastN,
138
+ verbose: options.verbose,
139
+ follow: options.follow,
140
+ };
141
+ }
142
+ /**
143
+ * Watch the session data (`peek --follow`, and the deprecated `tail`).
144
+ *
145
+ * @param options - Peek options (follow implied)
146
+ */
147
+ export async function followPreview(options) {
148
+ const following = { ...options, follow: true };
149
+ showBothSectionsWhenBothRequested(following);
150
+ const parsed = parsePeekOptions(following);
151
+ await runFollowMode(following, parsed, previewDisplayOptions(following, parsed.lastN));
152
+ }
97
153
  export function registerPeekCommand(program) {
98
154
  program
99
155
  .command('peek')
@@ -103,6 +159,7 @@ export function registerPeekCommand(program) {
103
159
  .option('-n, --network', 'Show only network requests', false)
104
160
  .option('-c, --console', 'Show only console messages', false)
105
161
  .option('-f, --follow', 'Watch for updates (like tail -f)', false)
162
+ .option('--interval <ms>', 'Refresh interval of --follow in ms, 100-60000 (default: 1000)')
106
163
  .option('--last <count>', 'Show last N items, 0 for all', '10')
107
164
  .option('--type <types>', 'Filter network requests by resource type (comma-separated: Document,XHR,Fetch,etc.)')
108
165
  .action(async (options) => {
@@ -110,26 +167,11 @@ export function registerPeekCommand(program) {
110
167
  if (options.network && !options.json) {
111
168
  console.error('Note: "bdg peek --network" is deprecated. Use "bdg network list" for enhanced filtering.');
112
169
  }
113
- let lastN;
114
- let resourceTypes;
115
- try {
116
- const parsed = parseOptions(options);
117
- lastN = parsed.lastN;
118
- resourceTypes = parsed.resourceTypes;
119
- }
120
- catch (error) {
121
- handleValidationError(error, options.json ?? false);
122
- }
123
- const baseOptions = {
124
- json: options.json,
125
- network: options.network,
126
- console: options.console,
127
- last: lastN,
128
- verbose: options.verbose,
129
- follow: options.follow,
130
- };
170
+ const parsed = parsePeekOptions(options);
171
+ const { lastN, resourceTypes } = parsed;
172
+ const baseOptions = previewDisplayOptions(options, lastN);
131
173
  if (options.follow) {
132
- await runFollowMode(options, lastN, resourceTypes, baseOptions);
174
+ await runFollowMode(options, parsed, baseOptions);
133
175
  return;
134
176
  }
135
177
  await runCommand(async () => {
@@ -93,8 +93,10 @@ export interface ScreenshotOptions {
93
93
  fullPage?: boolean;
94
94
  /** CSS selector for element capture */
95
95
  selector?: string;
96
- /** Cached element index (0-based) from previous query */
96
+ /** Cached element index (0-based) from previous query, or with `selector`, which of its matches */
97
97
  index?: number;
98
+ /** Extra space (CSS px) around an element capture */
99
+ padding?: number;
98
100
  /** Continuous capture mode to directory */
99
101
  follow?: boolean;
100
102
  /** Capture interval in ms for follow mode (string from CLI) */
@@ -373,6 +375,8 @@ export interface PeekCommandOptions extends BaseOptions, PreviewDisplayOptions {
373
375
  last?: string;
374
376
  /** Filter network requests by resource type (comma-separated) */
375
377
  type?: string;
378
+ /** Refresh interval of --follow in ms (string from CLI, default: 1000) */
379
+ interval?: string;
376
380
  }
377
381
  /**
378
382
  * Options for tail command.
@@ -26,6 +26,8 @@ export interface CollectorOptions {
26
26
  chromeFlags?: string;
27
27
  /** Viewport size, e.g. `1280x800`. */
28
28
  viewport?: string;
29
+ /** Emulate a phone (`--mobile`) */
30
+ mobile?: boolean;
29
31
  /** `prefers-color-scheme` to emulate: light or dark. */
30
32
  colorScheme?: string;
31
33
  }
@@ -53,6 +55,17 @@ export declare function extractUserDataDirFromFlags(flags: string[]): {
53
55
  * @returns The modified Command instance with all telemetry options applied
54
56
  */
55
57
  export declare function applyCollectorOptions(command: Command): Command;
58
+ /** Viewport of `--mobile` without `--viewport` (a common phone, CSS px) */
59
+ export declare const MOBILE_VIEWPORT: ViewportSize;
60
+ /**
61
+ * The viewport to emulate from `--viewport` and `--mobile`.
62
+ *
63
+ * @param viewport - `--viewport` value
64
+ * @param mobile - `--mobile` was given
65
+ * @returns Viewport (a phone's with `--mobile`), or undefined for neither
66
+ * @throws CommandError (81) for an invalid size
67
+ */
68
+ export declare function requestedViewport(viewport: string | undefined, mobile: boolean | undefined): ViewportSize | undefined;
56
69
  /**
57
70
  * Parse a `--viewport` value: width and height in CSS px joined by `x`
58
71
  * (`1280x800`; `X`, `×` and `,` work too).
@@ -81,7 +81,24 @@ export function applyCollectorOptions(command) {
81
81
  .addOption(jsonOption())
82
82
  .option('--chrome-flags <flags>', 'Custom Chrome flags (space-separated, e.g., --chrome-flags="--ignore-certificate-errors --disable-web-security")')
83
83
  .option('--viewport <WxH>', 'Viewport size in CSS px for the whole session, e.g. 1280x800 (default: 1920x1080 window)')
84
- .option('--color-scheme <scheme>', 'Emulate prefers-color-scheme for the session: light or dark (default: the system setting)');
84
+ .option('--color-scheme <scheme>', 'Emulate prefers-color-scheme for the session: light or dark (default: the system setting)')
85
+ .option('--mobile', `Emulate a phone for the session: mobile viewport (${MOBILE_VIEWPORT.width}x${MOBILE_VIEWPORT.height} unless --viewport), touch, mobile user agent`);
86
+ }
87
+ /** Viewport of `--mobile` without `--viewport` (a common phone, CSS px) */
88
+ export const MOBILE_VIEWPORT = { width: 390, height: 844 };
89
+ /**
90
+ * The viewport to emulate from `--viewport` and `--mobile`.
91
+ *
92
+ * @param viewport - `--viewport` value
93
+ * @param mobile - `--mobile` was given
94
+ * @returns Viewport (a phone's with `--mobile`), or undefined for neither
95
+ * @throws CommandError (81) for an invalid size
96
+ */
97
+ export function requestedViewport(viewport, mobile) {
98
+ const size = viewport !== undefined ? parseViewport(viewport) : undefined;
99
+ if (!mobile)
100
+ return size;
101
+ return { ...(size ?? MOBILE_VIEWPORT), mobile: true };
85
102
  }
86
103
  /** Largest viewport side accepted by `--viewport` (CSS px) */
87
104
  const MAX_VIEWPORT_SIDE = 10000;
@@ -157,7 +174,7 @@ function buildSessionOptions(options) {
157
174
  quiet: options.quiet ?? false,
158
175
  json: options.json ?? false,
159
176
  chromeFlags,
160
- viewport: options.viewport !== undefined ? parseViewport(options.viewport) : undefined,
177
+ viewport: requestedViewport(options.viewport, options.mobile),
161
178
  colorScheme: options.colorScheme !== undefined ? parseColorScheme(options.colorScheme) : undefined,
162
179
  };
163
180
  }
@@ -1,6 +1,12 @@
1
1
  /**
2
- * Tail command for continuous session monitoring.
2
+ * `bdg tail`: deprecated alias of `bdg peek --follow` (#115). It keeps its
3
+ * options and says to use `peek --follow` instead.
3
4
  */
4
5
  import type { Command } from 'commander';
6
+ /**
7
+ * Register the deprecated `tail` command.
8
+ *
9
+ * @param program - Root command
10
+ */
5
11
  export declare function registerTailCommand(program: Command): void;
6
12
  //# sourceMappingURL=tail.d.ts.map
@@ -1,43 +1,19 @@
1
1
  /**
2
- * Tail command for continuous session monitoring.
2
+ * `bdg tail`: deprecated alias of `bdg peek --follow` (#115). It keeps its
3
+ * options and says to use `peek --follow` instead.
4
+ */
5
+ import { followPreview } from './peek.js';
6
+ import { jsonOption } from './shared/commonOptions.js';
7
+ import { tailDeprecatedNotice } from '../ui/messages/preview.js';
8
+ /**
9
+ * Register the deprecated `tail` command.
10
+ *
11
+ * @param program - Root command
3
12
  */
4
- import { jsonOption, showBothSectionsWhenBothRequested } from './shared/commonOptions.js';
5
- import { noteFollowConnected } from './shared/daemonErrorHandler.js';
6
- import { fetchPreviewOutput } from './shared/dataFetcher.js';
7
- import { followFetchFailure, setupFollowMode, } from './shared/followMode.js';
8
- import { handleValidationError } from './shared/handleValidationError.js';
9
- import { MAX_LAST_ITEMS, positiveIntRule } from './shared/validation.js';
10
- import { formatPreview } from '../ui/formatters/preview.js';
11
- import { followingPreviewMessage, stoppedFollowingPreviewMessage } from '../ui/messages/preview.js';
12
- function parseOptions(options) {
13
- const lastRule = positiveIntRule({
14
- name: '--last',
15
- min: 1,
16
- max: MAX_LAST_ITEMS,
17
- default: 10,
18
- allowZeroForAll: true,
19
- });
20
- const intervalRule = positiveIntRule({ name: '--interval', min: 100, max: 60000, default: 1000 });
21
- return {
22
- lastN: lastRule.validate(options.last),
23
- interval: intervalRule.validate(options.interval),
24
- };
25
- }
26
- function createPreviewOptions(options, lastN) {
27
- return {
28
- json: options.json,
29
- network: options.network,
30
- console: options.console,
31
- last: lastN,
32
- verbose: options.verbose,
33
- follow: true,
34
- viewedAt: new Date(),
35
- };
36
- }
37
13
  export function registerTailCommand(program) {
38
14
  program
39
15
  .command('tail')
40
- .description('Continuously monitor session data (like tail -f)')
16
+ .description('Deprecated: use "bdg peek --follow" (same options)')
41
17
  .addOption(jsonOption())
42
18
  .option('-v, --verbose', 'Use verbose output with full URLs and formatting', false)
43
19
  .option('-n, --network', 'Show only network requests', false)
@@ -45,33 +21,8 @@ export function registerTailCommand(program) {
45
21
  .option('--last <count>', 'Show last N items (network requests + console messages), 0 for all', '10')
46
22
  .option('--interval <ms>', 'Update interval in milliseconds', '1000')
47
23
  .action(async (options) => {
48
- showBothSectionsWhenBothRequested(options);
49
- let lastN;
50
- let interval;
51
- try {
52
- const parsed = parseOptions(options);
53
- lastN = parsed.lastN;
54
- interval = parsed.interval;
55
- }
56
- catch (error) {
57
- handleValidationError(error, options.json ?? false);
58
- }
59
- const showPreview = async () => {
60
- const result = await fetchPreviewOutput({ lastN });
61
- if (!result.success) {
62
- return followFetchFailure(result, { json: options.json, retryIntervalMs: interval });
63
- }
64
- noteFollowConnected();
65
- if (!options.json)
66
- console.clear();
67
- console.log(formatPreview(result.data, createPreviewOptions(options, lastN)));
68
- return undefined;
69
- };
70
- await setupFollowMode(showPreview, {
71
- startMessage: followingPreviewMessage,
72
- stopMessage: stoppedFollowingPreviewMessage,
73
- intervalMs: interval,
74
- });
24
+ console.error(tailDeprecatedNotice());
25
+ await followPreview({ ...options, follow: true });
75
26
  });
76
27
  }
77
28
  //# sourceMappingURL=tail.js.map
package/dist/commands.js CHANGED
@@ -6,6 +6,7 @@ import { registerFormInteractionCommands } from './commands/dom/formInteraction.
6
6
  import { registerDomCommands } from './commands/dom/index.js';
7
7
  import { registerInstallSkillCommand } from './commands/installSkill.js';
8
8
  import { registerNetworkCommands } from './commands/network/index.js';
9
+ import { registerCssCommands } from './commands/css.js';
9
10
  import { registerPageCommands } from './commands/page.js';
10
11
  import { registerPeekCommand } from './commands/peek.js';
11
12
  import { registerSessionsCommand } from './commands/sessions.js';
@@ -39,6 +40,7 @@ export const commandRegistry = [
39
40
  registerDomCommands,
40
41
  registerFormInteractionCommands,
41
42
  registerPageCommands,
43
+ registerCssCommands,
42
44
  addCommandGroup('CDP Commands:'),
43
45
  registerCdpCommand,
44
46
  addCommandGroup('Network Commands:'),
@@ -4,6 +4,8 @@ import { createInteractionRunner } from './interactions.js';
4
4
  import { withTriggeredRequestCount } from './triggeredRequests.js';
5
5
  import { CommandError } from '../../errors/index.js';
6
6
  import { cdpCallError, formDiscoveryFailedError } from '../../errors/messages.js';
7
+ import { searchStyleSheets } from '../../runtime/css/search.js';
8
+ import { auditPage } from '../../runtime/dom/audit.js';
7
9
  import { evaluateScript, withBusyPageRecovery } from '../../runtime/dom/evalHelpers.js';
8
10
  import { inspectEventListeners } from '../../runtime/dom/eventListeners.js';
9
11
  import { FORM_DISCOVERY_SCRIPT, isRawFormData } from '../../runtime/dom/formDiscovery.js';
@@ -458,7 +460,11 @@ export function createCommandRegistry(store, emulation) {
458
460
  })), params.wait !== false))),
459
461
  dom_listeners: async (cdp, params) => withBusyPageRecovery(cdp, inspectEventListeners(cdp, params)),
460
462
  dom_layout: async (cdp, params) => withBusyPageRecovery(cdp, inspectLayout(cdp, params)),
461
- dom_inspect: async (cdp, params) => withBusyPageRecovery(cdp, inspectElement(cdp, params)),
463
+ dom_audit: async (cdp, params) => withBusyPageRecovery(cdp, auditPage(cdp, params)),
464
+ css_search: async (cdp, params) => searchStyleSheets(cdp, params),
465
+ dom_inspect: async (cdp, params) => withBusyPageRecovery(cdp, inspectElement(cdp, params).then((result) => result.theme === 'dark' && emulation.get().colorScheme === 'dark'
466
+ ? { ...result, themeFrom: 'emulation' }
467
+ : result)),
462
468
  dom_wait: async (cdp, params) => waitForCondition(cdp, params),
463
469
  page_navigate: async (cdp, params) => interact(cdp, () => navigatePage(cdp, params.action, {
464
470
  ...filterDefined({ url: params.url, wait: params.wait }),
@@ -4,6 +4,7 @@ import { prepareDOMCollection } from '../../telemetry/dom.js';
4
4
  import { startNavigationTracking } from '../../telemetry/navigation.js';
5
5
  import { startNetworkCollection, startWebSocketCollection } from '../../telemetry/network.js';
6
6
  import { getErrorMessage } from '../../utils/errors.js';
7
+ import { hideHeadlessUserAgent } from '../../runtime/page/userAgent.js';
7
8
  import { filterDefined } from '../../utils/objects.js';
8
9
  /** Delay before reading the title after a same-document navigation */
9
10
  const TITLE_REFRESH_DELAY_MS = 300;
@@ -83,56 +84,6 @@ async function emulatePageFocus(cdp, logger) {
83
84
  .send('Emulation.setFocusEmulationEnabled', { enabled: true })
84
85
  .catch((error) => logger.debug(`No focus emulation: ${getErrorMessage(error)}`));
85
86
  }
86
- /** Reads the client hints Chrome reports, renaming the HeadlessChrome brand */
87
- const USER_AGENT_METADATA_SCRIPT = `(async () => {
88
- const data = navigator.userAgentData;
89
- if (!data) return null;
90
- const values = await data.getHighEntropyValues(
91
- ['architecture', 'bitness', 'model', 'platformVersion', 'fullVersionList', 'wow64']
92
- );
93
- const rename = (list) => (list || []).map((entry) => ({
94
- brand: entry.brand.replace('HeadlessChrome', 'Google Chrome'),
95
- version: entry.version,
96
- }));
97
- return {
98
- brands: rename(values.brands),
99
- fullVersionList: rename(values.fullVersionList),
100
- platform: values.platform,
101
- platformVersion: values.platformVersion,
102
- architecture: values.architecture,
103
- model: values.model,
104
- mobile: values.mobile,
105
- bitness: values.bitness,
106
- wow64: values.wow64,
107
- };
108
- })()`;
109
- /**
110
- * Send the user agent and client hints of regular Chrome from headless
111
- * Chrome: sites serve "HeadlessChrome" a different page (or a bot
112
- * challenge), so the page would not be the one a user sees.
113
- *
114
- * @param cdp - CDP connection
115
- * @param logger - Logger for failures (the session works without it)
116
- */
117
- async function hideHeadlessUserAgent(cdp, logger) {
118
- try {
119
- const { userAgent } = (await cdp.send('Browser.getVersion'));
120
- if (!userAgent.includes('HeadlessChrome'))
121
- return;
122
- const metadata = (await cdp.send('Runtime.evaluate', {
123
- expression: USER_AGENT_METADATA_SCRIPT,
124
- awaitPromise: true,
125
- returnByValue: true,
126
- }));
127
- await cdp.send('Emulation.setUserAgentOverride', {
128
- userAgent: userAgent.replace('HeadlessChrome', 'Chrome'),
129
- ...(metadata.result?.value ? { userAgentMetadata: metadata.result.value } : {}),
130
- });
131
- }
132
- catch (error) {
133
- logger.debug(`User agent left as is: ${getErrorMessage(error)}`);
134
- }
135
- }
136
87
  export function createDefaultTelemetryPlugins() {
137
88
  return [
138
89
  {
@@ -145,9 +96,10 @@ export function createDefaultTelemetryPlugins() {
145
96
  {
146
97
  name: 'page-identity',
147
98
  runAlways: true,
148
- async start({ cdp, logger }) {
99
+ async start({ cdp, logger, config }) {
149
100
  await emulatePageFocus(cdp, logger);
150
- await hideHeadlessUserAgent(cdp, logger);
101
+ if (!config.viewport?.mobile)
102
+ await hideHeadlessUserAgent(cdp, logger);
151
103
  return () => undefined;
152
104
  },
153
105
  },