Skip to content

Commit 45ee2ee

Browse files
author
Ondrej Machala
committed
feat: add --headed flag for debugging
Adds `--headed` CLI option to run browser in visible mode. Useful for debugging slow captures or timeout issues. Closes #49
1 parent 7e34654 commit 45ee2ee

File tree

9 files changed

+44
-17
lines changed

9 files changed

+44
-17
lines changed

.changeset/feat-headed-option.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'heroshot': minor
3+
---
4+
5+
Add `--headed` flag to run browser in visible mode for debugging slow captures or timeout issues

docs/docs/cli.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ heroshot --workers 4 # Capture with 4 parallel workers
177177
| ------------------- | --------------------------------------------------------------------- |
178178
| `--clean` | Delete stale files from output directory (only works without pattern) |
179179
| `--workers <count>` | Number of parallel capture workers (default: 1) |
180+
| `--headed` | Show browser window (for debugging slow captures or timeouts) |
180181

181182
The pattern matches against:
182183

@@ -344,4 +345,8 @@ heroshot list --json
344345
# Verbose output (works with any command)
345346
heroshot -v
346347
heroshot config -v
348+
349+
# Debug slow captures with visible browser window
350+
heroshot --headed
351+
heroshot dashboard --headed
347352
```

src/cli/cli.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ program
7070
.option('--save', 'Save screenshot definition to config')
7171
.option('--clean', 'Delete stale files in output directory')
7272
.option('--workers <count>', 'Number of parallel capture workers', Number.parseInt)
73+
.option('--headed', 'Run browser in headed mode (visible window) for debugging')
7374
.action(async (url?: string, options?: ShotCommandOptions) => {
7475
const globalOptions = program.opts<GlobalOptions>();
7576
const success = await shotAction(url, options, globalOptions);

src/cli/handlers.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export async function handleUrlCapture(
6262
sessionKey,
6363
skipStaleCheck: true, // Don't check for stale files in oneshot mode
6464
viewportOnly: options?.viewportOnly,
65+
headed: options?.headed,
6566
});
6667

6768
if (options?.save && result.failed === 0) {
@@ -75,18 +76,23 @@ export async function handleUrlCapture(
7576
return result.failed === 0;
7677
}
7778

79+
type DefaultCommandOptions = {
80+
configPath: string;
81+
sessionKey: string | undefined;
82+
hasExplicitConfig: boolean;
83+
clean?: boolean;
84+
workers?: number;
85+
headed?: boolean;
86+
};
87+
7888
/**
7989
* Handle default command (setup or sync).
8090
*/
81-
export async function handleDefaultCommand(
82-
configPath: string,
83-
sessionKey: string | undefined,
84-
hasExplicitConfig: boolean,
85-
clean?: boolean,
86-
workers?: number
87-
): Promise<boolean> {
91+
export async function handleDefaultCommand(options: DefaultCommandOptions): Promise<boolean> {
92+
const { configPath, sessionKey, hasExplicitConfig, clean, workers, headed } = options;
93+
8894
if (existsSync(configPath)) {
89-
const result = await sync({ configPath, sessionKey, clean, workers });
95+
const result = await sync({ configPath, sessionKey, clean, workers, headed });
9096
return result.failed === 0;
9197
}
9298

@@ -97,7 +103,7 @@ export async function handleDefaultCommand(
97103

98104
const { hasScreenshots } = await setup();
99105
if (hasScreenshots) {
100-
const result = await sync({ clean, workers });
106+
const result = await sync({ clean, workers, headed });
101107
return result.failed === 0;
102108
}
103109
return true;
@@ -126,17 +132,19 @@ export async function shotAction(
126132
filter: url,
127133
clean: options?.clean,
128134
workers: options?.workers,
135+
headed: options?.headed,
129136
});
130137
return result.failed === 0;
131138
}
132139

133-
return handleDefaultCommand(
140+
return handleDefaultCommand({
134141
configPath,
135-
globalOptions.sessionKey,
136-
!!globalOptions.config,
137-
options?.clean,
138-
options?.workers
139-
);
142+
sessionKey: globalOptions.sessionKey,
143+
hasExplicitConfig: !!globalOptions.config,
144+
clean: options?.clean,
145+
workers: options?.workers,
146+
headed: options?.headed,
147+
});
140148
}
141149

142150
/**

src/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ export const shotCommandOptionsSchema = shotCliOptionsSchema.extend({
178178
clean: z.boolean().optional(),
179179
/** Number of parallel capture workers */
180180
workers: z.number().int().min(1).optional(),
181+
/** Run browser in headed mode (visible window) for debugging */
182+
headed: z.boolean().optional(),
181183
});
182184

183185
/** Global config */

src/sync/parallelCapture.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export type ParallelCaptureOptions = {
3232
bypassCSP?: boolean;
3333
reducedMotion?: 'reduce' | 'no-preference';
3434
userAgent?: string;
35+
headed?: boolean;
3536
};
3637
workers: number;
3738
captureSpinner: ReturnType<typeof spinner>;
@@ -96,7 +97,7 @@ async function executeBatch(
9697

9798
// Launch browser for this batch
9899
const { browser, context } = await launchBrowser({
99-
headless: true,
100+
headless: !browserOptions.headed,
100101
viewport: browserOptions.viewport,
101102
deviceScaleFactor: browserOptions.deviceScaleFactor,
102103
storageState: browserOptions.storageState,

src/sync/schemeCapture.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type CaptureSchemeOptions = {
2222
bypassCSP?: boolean;
2323
reducedMotion?: 'reduce' | 'no-preference';
2424
userAgent?: string;
25+
headed?: boolean;
2526
};
2627
colorScheme: 'light' | 'dark' | undefined;
2728
schemes: ('light' | 'dark')[];
@@ -49,7 +50,7 @@ export async function captureWithScheme(
4950
const results: ScreenshotResult[] = [];
5051

5152
const { browser, context } = await launchBrowser({
52-
headless: true,
53+
headless: !browserOptions.headed,
5354
viewport: browserOptions.viewport,
5455
deviceScaleFactor: browserOptions.deviceScaleFactor,
5556
storageState: browserOptions.storageState,

src/sync/sync.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type CaptureContext = {
3232
bypassCSP?: boolean;
3333
reducedMotion?: 'reduce' | 'no-preference';
3434
userAgent?: string;
35+
headed?: boolean;
3536
};
3637
schemes: ('light' | 'dark')[];
3738
workers: number;
@@ -130,6 +131,7 @@ export async function sync(options: SyncOptions = {}): Promise<SyncResult> {
130131
const browserOptions = {
131132
...buildBrowserOptions(config),
132133
storageState,
134+
headed: options.headed,
133135
};
134136

135137
// Start capture

src/sync/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ export type SyncOptions = {
8484
viewportOnly?: boolean;
8585
/** Number of parallel capture workers (default: 1) */
8686
workers?: number;
87+
/** Run browser in headed mode (visible window) for debugging */
88+
headed?: boolean;
8789
};
8890

8991
/** Padding dimensions */

0 commit comments

Comments
 (0)