mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-14 11:22:15 +01:00
feat(chromium): screenshot a single element via the selector form field (#947)
This commit is contained in:
@@ -69,6 +69,32 @@ Feature: /forms/chromium/screenshot/html
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "image/png"
|
||||
|
||||
# The target element is 300x200 and sits below a spacer, so a correct clip
|
||||
# proves both the element size and its page offset. See issue #947.
|
||||
Scenario: POST /forms/chromium/screenshot/html (Selector)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/chromium/screenshot/html" endpoint with the following form data and header(s):
|
||||
| files | testdata/screenshot-selector-html/index.html | file |
|
||||
| selector | #target | field |
|
||||
| Gotenberg-Output-Filename | foo | header |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "image/png"
|
||||
Then the "foo.png" image should be 300x200 pixels
|
||||
# A centered red pixel proves the clip landed on the element, not on the
|
||||
# white spacer above it, i.e. the page offset was applied.
|
||||
Then the "foo.png" image pixel at 150,100 should be "#ff0000"
|
||||
|
||||
Scenario: POST /forms/chromium/screenshot/html (Selector Not Found)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/chromium/screenshot/html" endpoint with the following form data and header(s):
|
||||
| files | testdata/screenshot-selector-html/index.html | file |
|
||||
| selector | #does-not-exist | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should contain string:
|
||||
"""
|
||||
The selector '#does-not-exist' (selector) matched no element with a visible box
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/chromium/screenshot/html (Quality)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/chromium/screenshot/html" endpoint with the following form data and header(s):
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/png" // Register the PNG decoder for image.DecodeConfig.
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
@@ -1010,6 +1012,51 @@ func (s *scenario) thePdfShouldHaveImages(ctx context.Context, name string, imag
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) theImageShouldBePixels(_ context.Context, name string, width, height int) error {
|
||||
path := fmt.Sprintf("%s/%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace"), name)
|
||||
|
||||
file, err := os.Open(path) //nolint:gosec // path is built from test-controlled values.
|
||||
if err != nil {
|
||||
return fmt.Errorf("open image %q: %w", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
config, format, err := image.DecodeConfig(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode image %q: %w", path, err)
|
||||
}
|
||||
|
||||
if config.Width != width || config.Height != height {
|
||||
return fmt.Errorf("expected %s image %dx%d, but actual is %dx%d", format, width, height, config.Width, config.Height)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) theImagePixelShouldBe(_ context.Context, name string, x, y int, want string) error {
|
||||
path := fmt.Sprintf("%s/%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace"), name)
|
||||
|
||||
file, err := os.Open(path) //nolint:gosec // path is built from test-controlled values.
|
||||
if err != nil {
|
||||
return fmt.Errorf("open image %q: %w", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
img, format, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode image %q: %w", path, err)
|
||||
}
|
||||
|
||||
r, g, b, _ := img.At(x, y).RGBA()
|
||||
// RGBA returns 16-bit channels; shift down to the 8-bit hex form.
|
||||
got := fmt.Sprintf("#%02x%02x%02x", r>>8, g>>8, b>>8)
|
||||
if !strings.EqualFold(got, want) {
|
||||
return fmt.Errorf("expected %s pixel at %d,%d to be %s, but actual is %s", format, x, y, want, got)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) thePdfShouldBeSetToLandscapeOrientation(ctx context.Context, name string, kind string) error {
|
||||
var path string
|
||||
if !strings.HasPrefix(name, "*_") {
|
||||
@@ -1599,6 +1646,8 @@ func InitializeScenario(ctx *godog.ScenarioContext) {
|
||||
ctx.Then(`^the "([^"]*)" PDF (should|should NOT) have the following content at page (\d+):$`, s.thePdfShouldHaveTheFollowingContentAtPage)
|
||||
ctx.Then(`^the "([^"]*)" PDF (should|should NOT) have content matching "([^"]*)" at page (\d+)$`, s.thePdfShouldHaveContentMatchingAtPage)
|
||||
ctx.Then(`^the "([^"]*)" PDF should have (\d+) image\(s\)$`, s.thePdfShouldHaveImages)
|
||||
ctx.Then(`^the "([^"]*)" image should be (\d+)x(\d+) pixels$`, s.theImageShouldBePixels)
|
||||
ctx.Then(`^the "([^"]*)" image pixel at (\d+),(\d+) should be "([^"]*)"$`, s.theImagePixelShouldBe)
|
||||
ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
|
||||
if s.gotenbergContainer != nil {
|
||||
errTerminate := s.gotenbergContainer.Terminate(ctx, testcontainers.StopTimeout(0))
|
||||
|
||||
28
test/integration/testdata/screenshot-selector-html/index.html
vendored
Normal file
28
test/integration/testdata/screenshot-selector-html/index.html
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Element screenshot</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 40px;
|
||||
background: #ffffff;
|
||||
}
|
||||
/* A spacer pushes the target away from the origin so the clip has to
|
||||
account for the element offset, not just its size. */
|
||||
.spacer {
|
||||
height: 120px;
|
||||
}
|
||||
#target {
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
background: #ff0000;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="spacer"></div>
|
||||
<div id="target"></div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user