@crouton-kit/capture 1.4.15 → 1.4.16

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/capture CHANGED
@@ -5910,6 +5910,7 @@ var targets_exports = {};
5910
5910
  __export(targets_exports, {
5911
5911
  AmbiguousPageTargetError: () => AmbiguousPageTargetError,
5912
5912
  closeTarget: () => closeTarget,
5913
+ findResponsivePageTabByUrl: () => findResponsivePageTabByUrl,
5913
5914
  findTab: () => findTab,
5914
5915
  findTabById: () => findTabById,
5915
5916
  findTabByIdAcrossEndpoints: () => findTabByIdAcrossEndpoints,
@@ -6049,6 +6050,25 @@ async function findTabByIdAcrossEndpoints(targetId, preferredPort) {
6049
6050
  const ports = resolveSearchPorts(await detectCdpPortsAsync(), preferredPort);
6050
6051
  return findTabByIdInPorts(targetId, ports);
6051
6052
  }
6053
+ async function findResponsivePageTabByUrl(port, url) {
6054
+ const pages = (await listTargets(port)).filter((target) => target.type === "page");
6055
+ const scores = pages.map((target) => scoreTabUrlMatch(target.url, url));
6056
+ const bestScore = Math.max(0, ...scores);
6057
+ if (bestScore === 0) return null;
6058
+ for (const target of pages.filter((_, index) => scores[index] === bestScore)) {
6059
+ if (!target.webSocketDebuggerUrl) continue;
6060
+ const client = new CDPClient(target.webSocketDebuggerUrl);
6061
+ try {
6062
+ await client.waitReady();
6063
+ await client.send("Page.captureScreenshot", { format: "png" }, 5e3);
6064
+ return target;
6065
+ } catch {
6066
+ } finally {
6067
+ client.close();
6068
+ }
6069
+ }
6070
+ return null;
6071
+ }
6052
6072
  async function findTabByUrlAcrossEndpoints(url, preferredPort) {
6053
6073
  const ports = resolveSearchPorts(await detectCdpPortsAsync(), preferredPort);
6054
6074
  let best = null;
@@ -10541,7 +10561,7 @@ function validateHarAppendBatch(value, source) {
10541
10561
  return { entries, incompleteLifecycles };
10542
10562
  }
10543
10563
  function captureVersion() {
10544
- const version = "1.4.15";
10564
+ const version = "1.4.16";
10545
10565
  return typeof version === "string" && version.length > 0 ? version : "development";
10546
10566
  }
10547
10567
  function token() {
@@ -12643,7 +12663,7 @@ function wallTime(value) {
12643
12663
  }
12644
12664
  }
12645
12665
  function captureVersion2() {
12646
- const version = "1.4.15";
12666
+ const version = "1.4.16";
12647
12667
  return typeof version === "string" && version.length > 0 ? version : "development";
12648
12668
  }
12649
12669
  function deepFreeze(value) {
@@ -15140,6 +15160,7 @@ async function start(parsed) {
15140
15160
  let bridgeSocket = null;
15141
15161
  let bridgePid = null;
15142
15162
  let cdpPort = null;
15163
+ let targetWasOpened = false;
15143
15164
  let selectedBrowser = null;
15144
15165
  let endpointSelectionReason = null;
15145
15166
  try {
@@ -15166,22 +15187,28 @@ async function start(parsed) {
15166
15187
  }
15167
15188
  }
15168
15189
  if (url) {
15190
+ let opened = false;
15169
15191
  try {
15170
15192
  target = await startWorld.openTab(cdpPort, url);
15193
+ opened = true;
15194
+ targetWasOpened = true;
15171
15195
  } catch (error4) {
15172
15196
  const message = error4 instanceof Error ? error4.message : String(error4);
15173
- if (message.toLowerCase().includes("not supported")) {
15197
+ if (!message.toLowerCase().includes("not supported")) throw error4;
15198
+ target = await startWorld.findResponsivePageTabByUrl?.(cdpPort, url) ?? null;
15199
+ if (!target) {
15174
15200
  throw worldFailure(
15175
- `Target.createTarget is unsupported on port ${cdpPort}. Use an existing tab: \`capture tab list --port ${cdpPort}\`, then \`capture session start --target <target-id> --port ${cdpPort}\`.`,
15201
+ `Target.createTarget is unsupported on port ${cdpPort}, and no matching existing page target answered a screenshot. Run \`capture tab list --port ${cdpPort}\` to inspect its targets.`,
15176
15202
  error4
15177
15203
  );
15178
15204
  }
15179
- throw error4;
15180
15205
  }
15181
- const openedPort = cdpPort;
15182
- const openedTargetId = target.id;
15183
- acquired.push({ label: "opened target", release: () => startWorld.closeTarget(openedPort, openedTargetId) });
15184
- pageLoadTimedOut = await startWorld.awaitTabReady(target, url);
15206
+ if (opened) {
15207
+ const openedPort = cdpPort;
15208
+ const openedTargetId = target.id;
15209
+ acquired.push({ label: "opened target", release: () => startWorld.closeTarget(openedPort, openedTargetId) });
15210
+ pageLoadTimedOut = await startWorld.awaitTabReady(target, url);
15211
+ }
15185
15212
  }
15186
15213
  if (hold) {
15187
15214
  const bridge = await startWorld.startBridge(dir, cdpPort);
@@ -15211,7 +15238,7 @@ async function start(parsed) {
15211
15238
  return;
15212
15239
  }
15213
15240
  const rows = [fact`bundle dir: ${dir}`];
15214
- if (target) rows.push(url ? fact`tab ${target.id} opened at ${url}` : fact`tab ${target.id} adopted at ${target.url}`);
15241
+ if (target) rows.push(targetWasOpened ? fact`tab ${target.id} opened at ${url}` : fact`tab ${target.id} adopted at ${target.url}`);
15215
15242
  if (cdpPort !== null) rows.push(
15216
15243
  parsed.portSource === "env" ? fact`CDP endpoint on port ${cdpPort} selected from ambient CDP_PORT; --port overrides endpoint selection and --target overrides tab selection.` : parsed.port !== void 0 ? fact`CDP endpoint on port ${cdpPort} selected by explicit --port; --port overrides endpoint selection and --target overrides tab selection.` : fact`${selectedBrowser ?? "CDP endpoint"} on port ${cdpPort} auto-selected because it ${endpointSelectionReason ?? "was selected by automatic endpoint detection"}; capture-launched browser recency does not affect selection. --port overrides endpoint selection and --target overrides tab selection.`
15217
15244
  );
@@ -16128,10 +16155,10 @@ use when collecting or reading the traffic, logs, and artifacts of scoped work a
16128
16155
 
16129
16156
  input:
16130
16157
  --url <url> Absolute URL to open in a fresh tab through CDP's
16131
- Target.createTarget. Some endpoints, including Electron,
16132
- expose existing tabs but reject Target.createTarget; on
16133
- those endpoints --url cannot be used. Conflicts with
16134
- --target.
16158
+ Target.createTarget. If an endpoint, including Electron,
16159
+ rejects Target.createTarget, capture adopts the matching
16160
+ existing page target that answers a screenshot. Conflicts
16161
+ with --target.
16135
16162
  --target <tab-id> Existing page tab to adopt. Resolves a full target id or
16136
16163
  an unambiguous prefix of at least four characters at the
16137
16164
  selected CDP endpoint; start fails if it does not resolve.
@@ -16277,6 +16304,10 @@ effects:
16277
16304
  const { findTabById: findTabById2 } = await Promise.resolve().then(() => (init_targets(), targets_exports));
16278
16305
  return findTabById2(port, targetId);
16279
16306
  },
16307
+ async findResponsivePageTabByUrl(port, url) {
16308
+ const { findResponsivePageTabByUrl: findResponsivePageTabByUrl2 } = await Promise.resolve().then(() => (init_targets(), targets_exports));
16309
+ return findResponsivePageTabByUrl2(port, url);
16310
+ },
16280
16311
  async closeTarget(port, targetId) {
16281
16312
  const { closeTarget: closeTarget2 } = await Promise.resolve().then(() => (init_targets(), targets_exports));
16282
16313
  return closeTarget2(port, targetId);
@@ -267328,7 +267359,7 @@ I/O contract: flags and positionals on input; one rendered prose block on stdout
267328
267359
  A CDP-enabled browser must be running; \`capture tab list\` is the probe, and \`capture tab launch\` starts one capture owns and reaps when nothing is (never hand-roll a detached browser \u2014 nothing reaps that). Capture never signals a browser it did not start, so attaching to your own with --port is unchanged. CDP_PORT / CDP_TARGET pin the browser + tab for target-selecting commands \u2014 precedence: explicit flag > active session > env. \`session start\` adopts only an explicit --target.`;
267329
267360
  }
267330
267361
  function printVersion() {
267331
- const declared = "1.4.15";
267362
+ const declared = "1.4.16";
267332
267363
  if (declared) {
267333
267364
  console.log(declared);
267334
267365
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/capture",
3
- "version": "1.4.15",
3
+ "version": "1.4.16",
4
4
  "description": "Browser automation and UI validation via CDP — session-based screenshots, HAR, a11y, click/type, and JS exec",
5
5
  "bin": {
6
6
  "capture": "./bin/capture"
@@ -205,6 +205,28 @@ export async function findTabByIdAcrossEndpoints(
205
205
  return findTabByIdInPorts(targetId, ports);
206
206
  }
207
207
 
208
+ export async function findResponsivePageTabByUrl(port: number, url: string): Promise<CDPTarget | null> {
209
+ const pages = (await listTargets(port)).filter((target) => target.type === 'page');
210
+ const scores = pages.map((target) => scoreTabUrlMatch(target.url, url));
211
+ const bestScore = Math.max(0, ...scores);
212
+ if (bestScore === 0) return null;
213
+
214
+ for (const target of pages.filter((_, index) => scores[index] === bestScore)) {
215
+ if (!target.webSocketDebuggerUrl) continue;
216
+ const client = new CDPClient(target.webSocketDebuggerUrl);
217
+ try {
218
+ await client.waitReady();
219
+ await client.send('Page.captureScreenshot', { format: 'png' }, 5_000);
220
+ return target;
221
+ } catch {
222
+ // Electron can expose a page target that never answers screenshot requests.
223
+ } finally {
224
+ client.close();
225
+ }
226
+ }
227
+ return null;
228
+ }
229
+
208
230
  export async function findTabByUrlAcrossEndpoints(
209
231
  url: string,
210
232
  preferredPort?: number,
@@ -391,10 +391,10 @@ const START_USAGE = `capture session start [--url <url> | --target <tab-id>] [--
391
391
 
392
392
  input:
393
393
  --url <url> Absolute URL to open in a fresh tab through CDP's
394
- Target.createTarget. Some endpoints, including Electron,
395
- expose existing tabs but reject Target.createTarget; on
396
- those endpoints --url cannot be used. Conflicts with
397
- --target.
394
+ Target.createTarget. If an endpoint, including Electron,
395
+ rejects Target.createTarget, capture adopts the matching
396
+ existing page target that answers a screenshot. Conflicts
397
+ with --target.
398
398
  --target <tab-id> Existing page tab to adopt. Resolves a full target id or
399
399
  an unambiguous prefix of at least four characters at the
400
400
  selected CDP endpoint; start fails if it does not resolve.
@@ -563,6 +563,7 @@ export interface SessionStartWorld {
563
563
  detectCdpPort(): Promise<number | { port: number; app: string; selectionReason: string }>;
564
564
  openTab(port: number, url: string): Promise<CDPTarget>;
565
565
  findTabById(port: number, targetId: string): Promise<CDPTarget | null>;
566
+ findResponsivePageTabByUrl?(port: number, url: string): Promise<CDPTarget | null>;
566
567
  closeTarget(port: number, targetId: string): Promise<void>;
567
568
  /** Attach to the freshly-opened tab and wait for load; returns page-load-timed-out. */
568
569
  awaitTabReady(target: CDPTarget, url: string): Promise<boolean>;
@@ -588,6 +589,10 @@ const productionStartWorld: SessionStartWorld = {
588
589
  const { findTabById } = await import('../cdp/targets.js');
589
590
  return findTabById(port, targetId);
590
591
  },
592
+ async findResponsivePageTabByUrl(port, url) {
593
+ const { findResponsivePageTabByUrl } = await import('../cdp/targets.js');
594
+ return findResponsivePageTabByUrl(port, url);
595
+ },
591
596
  async closeTarget(port, targetId) {
592
597
  const { closeTarget } = await import('../cdp/targets.js');
593
598
  return closeTarget(port, targetId);
@@ -692,6 +697,7 @@ async function start(parsed: ParsedArgs): Promise<void> {
692
697
  let bridgeSocket: string | null = null;
693
698
  let bridgePid: number | null = null;
694
699
  let cdpPort: number | null = null;
700
+ let targetWasOpened = false;
695
701
  let selectedBrowser: string | null = null;
696
702
  let endpointSelectionReason: string | null = null;
697
703
 
@@ -723,22 +729,28 @@ async function start(parsed: ParsedArgs): Promise<void> {
723
729
  }
724
730
 
725
731
  if (url) {
732
+ let opened = false;
726
733
  try {
727
734
  target = await startWorld.openTab(cdpPort!, url);
735
+ opened = true;
736
+ targetWasOpened = true;
728
737
  } catch (error) {
729
738
  const message = error instanceof Error ? error.message : String(error);
730
- if (message.toLowerCase().includes('not supported')) {
739
+ if (!message.toLowerCase().includes('not supported')) throw error;
740
+ target = await startWorld.findResponsivePageTabByUrl?.(cdpPort!, url) ?? null;
741
+ if (!target) {
731
742
  throw worldFailure(
732
- `Target.createTarget is unsupported on port ${cdpPort}. Use an existing tab: \`capture tab list --port ${cdpPort}\`, then \`capture session start --target <target-id> --port ${cdpPort}\`.`,
743
+ `Target.createTarget is unsupported on port ${cdpPort}, and no matching existing page target answered a screenshot. Run \`capture tab list --port ${cdpPort}\` to inspect its targets.`,
733
744
  error,
734
745
  );
735
746
  }
736
- throw error;
737
747
  }
738
- const openedPort = cdpPort!;
739
- const openedTargetId = target.id;
740
- acquired.push({ label: 'opened target', release: () => startWorld.closeTarget(openedPort, openedTargetId) });
741
- pageLoadTimedOut = await startWorld.awaitTabReady(target, url);
748
+ if (opened) {
749
+ const openedPort = cdpPort!;
750
+ const openedTargetId = target.id;
751
+ acquired.push({ label: 'opened target', release: () => startWorld.closeTarget(openedPort, openedTargetId) });
752
+ pageLoadTimedOut = await startWorld.awaitTabReady(target, url);
753
+ }
742
754
  }
743
755
 
744
756
  // Hold a CDP browser connection open for the session's lifetime so
@@ -778,7 +790,7 @@ async function start(parsed: ParsedArgs): Promise<void> {
778
790
  }
779
791
 
780
792
  const rows: FactLine[] = [fact`bundle dir: ${dir}`];
781
- if (target) rows.push(url ? fact`tab ${target.id} opened at ${url}` : fact`tab ${target.id} adopted at ${target.url}`);
793
+ if (target) rows.push(targetWasOpened ? fact`tab ${target.id} opened at ${url}` : fact`tab ${target.id} adopted at ${target.url}`);
782
794
  if (cdpPort !== null) rows.push(
783
795
  parsed.portSource === 'env'
784
796
  ? fact`CDP endpoint on port ${cdpPort} selected from ambient CDP_PORT; --port overrides endpoint selection and --target overrides tab selection.`