├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── README.md ├── example.js ├── example.png ├── index.js ├── package-lock.json ├── package.json ├── script └── render.js └── test.js /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | - name: npm install 9 | run: npm ci 10 | - name: npm test 11 | uses: GabrielBB/xvfb-action@v1.0 12 | with: 13 | run: npm test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | example-stream.png 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example.png 2 | test.js 3 | .github/workflows/ci.yml 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # capture-electron 2 | 3 | Capture screenshots using [electron](http://electron.atom.io/). 4 | 5 | ![CI](https://github.com/juliangruber/capture-electron/workflows/CI/badge.svg) 6 | 7 | ## Features 8 | 9 | - Promise and Stream interfaces 10 | - Configurable Viewport 11 | - Waits for `DOMContentLoaded` 12 | - File formats `png`, `jpg` and `bmp` 13 | 14 | ## Example 15 | 16 | Capture a `800x600` screenshot of [github.com](http://github.com): 17 | 18 | ```js 19 | const capture = require('capture-electron') 20 | const fs = require('fs') 21 | 22 | capture({ 23 | url: 'https://github.com/', 24 | width: 800, 25 | height: 600 26 | }).then(screenshot => { 27 | fs.writeFileSync(`${__dirname}/example.png`, screenshot) 28 | console.log('open example.png') 29 | }) 30 | ``` 31 | 32 | ![github.com](https://raw.github.com/juliangruber/capture-electron/master/example.png) 33 | 34 | ## API 35 | 36 | ### screenshot({ url, width = 1024, height = 768, wait = 0, format = 'png' }) 37 | 38 | Capture a screenshot of `url`, returns a `Promise` which resolves with a buffer. 39 | 40 | Options: 41 | 42 | - `url` Page url 43 | - `width` Viewport width 44 | - `height` Viewport height 45 | - `wait` Time in `ms` to wait after the `DOMContentLoaded` event 46 | - `format` File format (`png`, `jpg`, `bmp`) 47 | 48 | ### screenshot.stream(options) 49 | 50 | Takes the same options as above, but returns a stream instead. 51 | 52 | Example: 53 | 54 | ```js 55 | capture 56 | .stream({ 57 | url: 'https://github.com/', 58 | width: 800, 59 | height: 600 60 | }) 61 | .pipe(fs.createWriteStream(`${__dirname}/example-stream.png`)) 62 | .on('close', () => console.log('open example-stream.png')) 63 | ``` 64 | 65 | ## Installation 66 | 67 | With [npm](https://npmjs.org) do: 68 | 69 | ```bash 70 | npm install capture-electron 71 | ``` 72 | 73 | ## CI 74 | 75 | This project requires an `xvfb` setup to be running in your CI environment. 76 | For an example how to set one up, check out the [.travis.yml](https://github.com/juliangruber/capture-electron/blob/master/.travis.yml). 77 | After that, no further setup is required however, as the electron executable is 78 | installed automatically. 79 | 80 | ## Related projects 81 | 82 | - __[capture-screenshot](https://github.com/juliangruber/capture-screenshot)__ — Capture screenshots in multiple browsers 83 | - __[capture-chrome](https://github.com/juliangruber/capture-chrome)__ — Capture screenshots using Chrome 84 | - __[capture-phantomjs](https://github.com/juliangruber/capture-phantomjs)__ — Capture screenshots using PhantomJS 85 | 86 | ## Sponsors 87 | 88 | This module is proudly supported by my [Sponsors](https://github.com/juliangruber/sponsors)! 89 | 90 | Do you want to support modules like this to improve their quality, stability and weigh in on new features? Then please consider donating to my [Patreon](https://www.patreon.com/juliangruber). Not sure how much of my modules you're using? Try [feross/thanks](https://github.com/feross/thanks)! 91 | 92 | ## License 93 | 94 | (MIT) 95 | 96 | Copyright (c) 2017 Julian Gruber <julian@juliangruber.com> 97 | 98 | Permission is hereby granted, free of charge, to any person obtaining a copy of 99 | this software and associated documentation files (the "Software"), to deal in 100 | the Software without restriction, including without limitation the rights to 101 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 102 | of the Software, and to permit persons to whom the Software is furnished to do 103 | so, subject to the following conditions: 104 | 105 | The above copyright notice and this permission notice shall be included in all 106 | copies or substantial portions of the Software. 107 | 108 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 109 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 110 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 111 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 112 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 113 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 114 | SOFTWARE. 115 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const capture = require('.') 2 | const fs = require('fs') 3 | 4 | capture({ 5 | url: 'https://github.com/', 6 | width: 800, 7 | height: 600 8 | }).then(screenshot => { 9 | fs.writeFileSync(`${__dirname}/example.png`, screenshot) 10 | console.log('open example.png') 11 | }) 12 | 13 | capture 14 | .stream({ 15 | url: 'https://github.com/', 16 | width: 800, 17 | height: 600 18 | }) 19 | .pipe(fs.createWriteStream(`${__dirname}/example-stream.png`)) 20 | .on('close', () => console.log('open example-stream.png')) 21 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliangruber/capture-electron/8b01091e343be6861a9241cac7f98126bac0eb75/example.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util') 2 | const exec = promisify(require('child_process').exec) 3 | const { spawn } = require('child_process') 4 | const electron = require('electron') 5 | const escape = require('shell-escape') 6 | 7 | module.exports = ({ 8 | url, 9 | width: width = 1024, 10 | height: height = 768, 11 | wait: wait = 0, 12 | format: format = 'png' 13 | }) => 14 | exec( 15 | escape([ 16 | electron, 17 | `${__dirname}/script/render.js`, 18 | url, 19 | width, 20 | height, 21 | wait, 22 | format.toLowerCase() 23 | ]), 24 | { maxBuffer: Infinity, encoding: 'buffer' } 25 | ).then(({ stdout }) => stdout) 26 | 27 | module.exports.stream = ({ 28 | url, 29 | width: width = 1024, 30 | height: height = 768, 31 | wait: wait = 0, 32 | format: format = 'png' 33 | }) => 34 | spawn(electron, [ 35 | `${__dirname}/script/render.js`, 36 | url, 37 | width, 38 | height, 39 | wait, 40 | format.toLowerCase() 41 | ]).stdout 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "capture-electron", 3 | "description": "Capture screenshots using electron", 4 | "version": "4.0.3", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/juliangruber/capture-electron.git" 8 | }, 9 | "scripts": { 10 | "test": "tap --reporter=tap test.js && npm run lint", 11 | "lint": "prettier-standard '**/*.js' && standard" 12 | }, 13 | "homepage": "https://github.com/juliangruber/capture-electron", 14 | "main": "index.js", 15 | "dependencies": { 16 | "electron": "^15.1.2", 17 | "safe-buffer": "^5.1.1", 18 | "shell-escape": "^0.2.0" 19 | }, 20 | "devDependencies": { 21 | "prettier-standard": "^8.0.1", 22 | "standard": "^11.0.1", 23 | "tap": "^12.0.1" 24 | }, 25 | "keywords": [ 26 | "electron", 27 | "screenshot", 28 | "headless" 29 | ], 30 | "author": { 31 | "name": "Julian Gruber", 32 | "email": "mail@juliangruber.com", 33 | "url": "http://juliangruber.com" 34 | }, 35 | "license": "MIT" 36 | } 37 | -------------------------------------------------------------------------------- /script/render.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require('electron') 2 | 3 | const url = process.argv[2] 4 | const width = Number(process.argv[3]) 5 | const height = Number(process.argv[4]) 6 | const wait = Number(process.argv[5]) 7 | const format = process.argv[6] 8 | 9 | let win 10 | 11 | app.on('ready', () => { 12 | win = new BrowserWindow({ width, height, show: false }) 13 | win.loadURL(url) 14 | win.on('closed', () => { 15 | win = null 16 | }) 17 | win.webContents.on('did-finish-load', () => { 18 | setTimeout(() => { 19 | win.capturePage().then(img => { 20 | const buf = 21 | format === 'jpg' || format === 'jpeg' 22 | ? img.toJPEG(100) 23 | : format === 'bmp' ? img.toBitmap() : img.toPNG() 24 | const written = process.stdout.write(buf) 25 | if (written) process.exit() 26 | else process.stdout.on('drain', () => process.exit()) 27 | }) 28 | }, wait) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const { test } = require('tap') 2 | const screenshot = require('.') 3 | const http = require('http') 4 | 5 | let server, url 6 | 7 | test('setup', t => { 8 | server = http.createServer((req, res) => res.end('ohai!')) 9 | server.listen(() => { 10 | url = `http://localhost:${server.address().port}` 11 | t.end() 12 | }) 13 | }) 14 | 15 | test('promise', async t => { 16 | const pic = await screenshot({ url }) 17 | t.ok(pic) 18 | t.ok(Buffer.isBuffer(pic)) 19 | }) 20 | 21 | test('stream', t => { 22 | t.plan(2) 23 | screenshot 24 | .stream({ url }) 25 | .once('data', chunk => t.ok(Buffer.isBuffer(chunk))) 26 | .on('end', () => t.ok(true)) 27 | }) 28 | 29 | test('cleanup', t => { 30 | server.close() 31 | t.end() 32 | }) 33 | --------------------------------------------------------------------------------