88 lines
2.6 KiB
Go
88 lines
2.6 KiB
Go
// Package shot captures a PNG screenshot of a rendered CMS page using a
|
|
// headless Chromium (chromedp / CDP). It is the image-capture half of the
|
|
// `ninja theme screenshot` harness; dojo's Playwright runner only ingests
|
|
// pass/fail JSON, so capture lives here.
|
|
package shot
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/chromedp/chromedp"
|
|
)
|
|
|
|
// Options configures a single screenshot capture.
|
|
type Options struct {
|
|
URL string // full page URL to navigate to
|
|
Width int // viewport width in px
|
|
Height int // viewport height in px
|
|
FullPage bool // capture the full scroll height, not just the viewport
|
|
WaitSelector string // CSS selector to wait for before capturing (e.g. "section")
|
|
Timeout time.Duration // overall navigation+capture timeout
|
|
}
|
|
|
|
// PreviewURL composes the gallery-page URL with the render-only override.
|
|
// host is the gallery site base (scheme+host), slug is the page path ("/"),
|
|
// theme is the bare theme key written to ?preview_template.
|
|
func PreviewURL(host, slug, theme string) string {
|
|
host = strings.TrimRight(host, "/")
|
|
if slug == "" {
|
|
slug = "/"
|
|
}
|
|
if !strings.HasPrefix(slug, "/") {
|
|
slug = "/" + slug
|
|
}
|
|
return fmt.Sprintf("%s%s?preview_template=%s", host, slug, url.QueryEscape(theme))
|
|
}
|
|
|
|
// Capture navigates to opts.URL in a headless Chromium and returns PNG bytes.
|
|
func Capture(ctx context.Context, opts Options) ([]byte, error) {
|
|
if opts.Width == 0 {
|
|
opts.Width = 1440
|
|
}
|
|
if opts.Height == 0 {
|
|
opts.Height = 900
|
|
}
|
|
if opts.Timeout == 0 {
|
|
opts.Timeout = 30 * time.Second
|
|
}
|
|
|
|
allocCtx, cancelAlloc := chromedp.NewExecAllocator(ctx,
|
|
append(chromedp.DefaultExecAllocatorOptions[:],
|
|
chromedp.WindowSize(opts.Width, opts.Height),
|
|
chromedp.Flag("headless", true),
|
|
)...,
|
|
)
|
|
defer cancelAlloc()
|
|
|
|
browserCtx, cancelBrowser := chromedp.NewContext(allocCtx)
|
|
defer cancelBrowser()
|
|
|
|
timeoutCtx, cancelTimeout := context.WithTimeout(browserCtx, opts.Timeout)
|
|
defer cancelTimeout()
|
|
|
|
var buf []byte
|
|
tasks := chromedp.Tasks{
|
|
chromedp.EmulateViewport(int64(opts.Width), int64(opts.Height)),
|
|
chromedp.Navigate(opts.URL),
|
|
}
|
|
if opts.WaitSelector != "" {
|
|
tasks = append(tasks, chromedp.WaitVisible(opts.WaitSelector, chromedp.ByQuery))
|
|
} else {
|
|
tasks = append(tasks, chromedp.WaitReady("body", chromedp.ByQuery))
|
|
}
|
|
if opts.FullPage {
|
|
tasks = append(tasks, chromedp.FullScreenshot(&buf, 90))
|
|
} else {
|
|
tasks = append(tasks, chromedp.CaptureScreenshot(&buf))
|
|
}
|
|
|
|
if err := chromedp.Run(timeoutCtx, tasks); err != nil {
|
|
return nil, fmt.Errorf("capture %s: %w", opts.URL, err)
|
|
}
|
|
return buf, nil
|
|
}
|