Concurrent screenshot capture library for Go, powered by Selenium WebDriver and a built-in worker pool.
- Browser support — Chrome (via ChromeDriver) and Firefox (via GeckoDriver)
- Concurrency — built-in worker pool with one isolated WebDriver session per worker
- Element targeting — capture specific DOM elements by ID, XPath, CSS selector, tag name, etc.
- Flexible output — raw
[]bytepassed to your handler (save to disk, upload, encode — up to you) - Error reporting — every job returns its result over a channel
| Dependency | Purpose |
|---|---|
| ChromeDriver or GeckoDriver | Browser automation driver |
| Chrome or Firefox | Target browser binary |
go get github.com/pog7x/ssfactorypackage main
import "github.com/pog7x/ssfactory"
func main() {
f, stop, err := ssfactory.NewFactory(ssfactory.InitFactory{
WebdriverPort: 9515,
UseBrowser: ssfactory.ChromeName,
ChromeBinaryPath: "/usr/bin/google-chrome",
ChromedriverPath: "/usr/local/bin/chromedriver",
ChromeArgs: []string{"--headless", "--no-sandbox", "--disable-dev-shm-usage"},
WorkersCount: 4,
})
if err != nil {
panic(err)
}
defer stop()
maximize := ""
errCh := f.MakeScreenshot(ssfactory.MakeScreenshotPayload{
URL: "https://example.com",
DOMElementBy: ssfactory.ByTagName,
DOMElementName: "body",
Scroll: true,
MaximizeWindow: &maximize,
BytesHandler: func(b []byte) error {
// save, upload, encode to PNG, etc.
return nil
},
})
if err := <-errCh; err != nil {
panic(err)
}
}MakeScreenshot is asynchronous: it queues the job and returns a buffered
channel that receives the result (nil on success) and is then closed. Reading
from the channel is optional — it never blocks the worker.
f, stop, err := ssfactory.NewFactory(ssfactory.InitFactory{
WebdriverPort: 8080,
UseBrowser: ssfactory.FirefoxName,
FirefoxBinaryPath: "/usr/bin/firefox",
GeckodriverPath: "/usr/local/bin/geckodriver",
FirefoxArgs: []string{"--headless", "--width=1920"},
WorkersCount: 2,
})Each worker owns a dedicated WebDriver session for the whole navigate → wait → find → capture sequence, so parallel jobs never share a browser tab and cannot overwrite each other's page.
That means WorkersCount sessions are opened up front, one browser process
each — plan memory accordingly.
| Browser | Driver processes | Ports used |
|---|---|---|
| Chrome | one chromedriver serving all sessions |
WebdriverPort |
| Firefox | one geckodriver per session — it only supports a single session at a time |
WebdriverPort … WebdriverPort+WorkersCount-1 |
So with Firefox and WorkersCount: 2 starting at port 8080, ports 8080 and
8081 must both be free.
stop() drains the pool — it waits for queued and in-flight jobs to finish
before closing the sessions and shutting the drivers down — and returns any
teardown error. Jobs submitted after stop() fail with ErrFactoryStopped.
| Constant | Selenium strategy |
|---|---|
ByID |
id |
ByXPATH |
xpath |
ByLinkText |
link text |
ByPartialLinkText |
partial link text |
ByName |
name |
ByTagName |
tag name |
ByClassName |
class name |
ByCSSSelector |
css selector |
| Field | Type | Description |
|---|---|---|
URL |
string |
Page URL to navigate to |
DOMElementBy |
string |
Selector strategy (see table above) |
DOMElementName |
string |
Selector value |
Scroll |
bool |
Scroll element into view before capture |
MaximizeWindow |
*string |
Maximize window handle (pass nil to skip) |
Timeout |
time.Duration |
Max time to wait for the target element to appear (0 — don't wait) |
BytesHandler |
func([]byte) error |
Callback receiving screenshot bytes |
URL, DOMElementBy, DOMElementName and BytesHandler are required; a
payload missing any of them fails with a validation error on the result channel.
MIT — Copyright (c) 2022 pog7x