Merge pull request #4 from runablehq/shashwat-viewportScreenshot

chore: viewport option for screenshot
This commit is contained in:
Saksham
2026-05-01 13:52:35 +05:30
committed by GitHub
5 changed files with 54 additions and 3 deletions
+6
View File
@@ -68,6 +68,7 @@ Navigation:
Observe:
text [selector] Visible text (selector uses querySelector, default: body)
shot [file] Screenshot (default: ./shot.png)
--width/--height temporarily resize viewport for the shot
snap Interactive elements via Accessibility Tree
Interact:
@@ -99,6 +100,8 @@ Flags:
--json structured output (snap, tab list, logs, audit)
--right right-click
--double double-click
--width <px> screenshot viewport width
--height <px> screenshot viewport height
--fps <n> recording frame rate (default: 30)
--scale <n> recording scale factor (default: 1)
```
@@ -115,6 +118,9 @@ command runs. For heavy SPAs that fetch data after mount, follow up with
### Observation
`shot --width <px> --height <px>` temporarily resizes the viewport to the
given dimensions for the screenshot, then restores the original viewport.
`text` calls `querySelector` — returns first match only. `text "p"` may return
empty if the first `<p>` on the page is empty. Use a scoped selector like
`text "main"` or `text "#content"` for better results on noisy pages.
+1
View File
@@ -154,6 +154,7 @@ mb wait url:/dashboard
```bash
mb shot page.png
mb shot page.png --width 1440 --height 900
```
### Extract text
+31 -3
View File
@@ -1,10 +1,38 @@
import { connect } from "../lib/browser"
import type { Flags } from "../lib/flags"
interface ViewportFlagsInput {
flags: Flags
}
const getViewportSize = ({ flags }: ViewportFlagsInput) => {
const { width, height } = flags
const hasWidth = width !== undefined
const hasHeight = height !== undefined
if (!hasWidth && !hasHeight) return undefined
if (!hasWidth || !hasHeight) {
throw new Error("--width and --height must be provided together.")
}
return { width: width!, height: height! }
}
export const shot = async (args: string[], flags: Flags) => {
const path = args[0] || "./shot.png"
const viewportSize = getViewportSize({ flags })
const { page, close } = await connect(flags.tab)
await page.screenshot({ path, type: "png" })
console.log(path)
await close()
const originalViewport = page.viewport()
try {
if (viewportSize) {
await page.setViewport(viewportSize)
}
await page.screenshot({ path, type: "png" })
console.log(path)
} finally {
if (viewportSize && originalViewport) {
await page.setViewport(originalViewport)
}
await close()
}
}
+3
View File
@@ -35,6 +35,7 @@ Navigation:
Observe:
shot [file] Screenshot (default: ./shot.png)
--width/--height temporarily resize viewport for the shot
snap Interactive elements with (x, y)
text [selector] Visible text content
@@ -68,6 +69,8 @@ Flags:
--tab <id> Stable Chrome target id (default: first page)
--json JSON output
--right/--double Right/double click
--width <px> Screenshot viewport width
--height <px> Screenshot viewport height
--fps <n> Recording frame rate (default: 30)
--scale <n> Recording scale factor (default: 1)`
+13
View File
@@ -21,6 +21,15 @@ const parseNumericFlag = ({ name, value }: NumericFlagInput) => {
return numeric
}
const parseOptionalPositiveIntegerFlag = ({ name, value }: NumericFlagInput) => {
if (value === undefined) return undefined
const numeric = parseNumericFlag({ name, value })
if (!Number.isInteger(numeric) || numeric <= 0) {
throw new Error(`Invalid value for --${name}: "${String(value)}". Expected a positive integer.`)
}
return numeric
}
const formatParseArgsError = ({ error }: FormatParseArgsErrorInput) => {
const message = error instanceof Error ? error.message : String(error)
const unknownFlag = message.match(/Unknown option '([^']+)'/)
@@ -45,6 +54,8 @@ export const parse = (argv: string[]) => {
help: { type: "boolean", short: "h", default: false },
fps: { type: "string" },
scale: { type: "string" },
width: { type: "string" },
height: { type: "string" },
},
})
} catch (error) {
@@ -65,6 +76,8 @@ export const parse = (argv: string[]) => {
help: values.help!,
fps: values.fps ? parseNumericFlag({ name: "fps", value: values.fps }) : undefined,
scale: values.scale ? parseNumericFlag({ name: "scale", value: values.scale }) : undefined,
width: parseOptionalPositiveIntegerFlag({ name: "width", value: values.width }),
height: parseOptionalPositiveIntegerFlag({ name: "height", value: values.height }),
},
}
}