diff --git a/.gitignore b/.gitignore index 3c3629e..239ecff 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +yarn.lock diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.travis.yml b/.travis.yml index 57505cf..651cec7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,3 @@ sudo: false language: node_js node_js: - '8' - - '6' - - '4' diff --git a/fixtures/script.js b/fixtures/script.js index 7755959..33d225a 100644 --- a/fixtures/script.js +++ b/fixtures/script.js @@ -1 +1 @@ -document.querySelector('.mobile-bar').style.backgroundColor = 'green'; +document.querySelector('div').style.backgroundColor = 'red'; diff --git a/fixtures/server.js b/fixtures/server.js index d432652..9e8ec29 100644 --- a/fixtures/server.js +++ b/fixtures/server.js @@ -1,46 +1,56 @@ 'use strict'; -const http = require('http'); -const cookie = require('cookie'); -const getPort = require('get-port'); +const createTestServer = require('create-test-server'); const pify = require('pify'); +const toughCookie = require('tough-cookie'); -module.exports = opts => { - opts = opts || {}; - - return getPort().then(port => { - const s = http.createServer((req, res) => { - setTimeout(() => { - s.emit(req.url, req, res); - }, (opts.delay || 0) * 1000); - }); - - s.port = port; - s.url = `http://localhost:${port}`; - s.close = pify(s.close); - - s.on('/', (req, res) => { - res.writeHead(200, {'Content-Type': 'text/html'}); - res.end(''); - }); - - s.on('/cookies', (req, res) => { - const color = cookie.parse(req.headers.cookie).color || 'white'; - const style = [ - `background-color: ${color}; position: absolute;`, - 'top: 0; right: 0; bottom: 0; left: 0;' - ].join(' '); - - res.writeHead(200, {'Content-Type': 'text/html'}); - res.end(`
`); - }); - - s.on('/redirect', (req, res) => { - res.writeHead(302, {location: `http://localhost:${port}/`}); - res.end(); - }); +module.exports = () => createTestServer().then(server => { + server.get('/', (req, res) => { + const style = `background-color: black; width: 100px; height: 100px;`; + res.send(`
`); + }); + + server.get('/delay', (req, res) => { + const style = `width: 100px; height: 100px;`; + res.send(` + +
+ + + `); + }); - s.listen(port); + server.get('/cookie', (req, res) => { + const color = toughCookie.parse(req.headers.cookie).value || 'white'; + const style = ` + background-color: ${color}; + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + `; + + res.send(`
`); + }); - return s; + server.get('/headers', (req, res) => { + server.emit('headers', req); + res.end(); }); -}; + + server.get('/redirect', (req, res) => { + res.redirect(server.url); + }); + + server.get('/timeout/:delay', (req, res) => { + setTimeout(() => { + res.end(); + }, req.params.delay * 1000); + }); + + return server; +}); diff --git a/fixtures/style.css b/fixtures/style.css index 458c5de..cc893dd 100644 --- a/fixtures/style.css +++ b/fixtures/style.css @@ -1,3 +1,3 @@ -.mobile-bar { - background-color: green !important; +div { + background-color: red !important; } diff --git a/fixtures/test-delay-element.html b/fixtures/test-delay-element.html deleted file mode 100644 index b3bd269..0000000 --- a/fixtures/test-delay-element.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - -
- - - diff --git a/fixtures/test-error-script.html b/fixtures/test-error-script.html deleted file mode 100644 index 2d86bc4..0000000 --- a/fixtures/test-error-script.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -
- - - diff --git a/fixtures/test-hide-element.html b/fixtures/test-hide-element.html deleted file mode 100644 index 62a74e1..0000000 --- a/fixtures/test-hide-element.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - -
- - diff --git a/index.js b/index.js index 0185d6e..c1ec413 100644 --- a/index.js +++ b/index.js @@ -1,85 +1,117 @@ 'use strict'; -const fs = require('fs'); -const path = require('path'); -const urlMod = require('url'); -const base64Stream = require('base64-stream'); -const parseCookiePhantomjs = require('parse-cookie-phantomjs'); -const phantomBridge = require('phantom-bridge'); -const byline = require('byline'); - -const handleCookies = (cookies, url) => { - const parsedUrl = urlMod.parse(url); - - return (cookies || []).map(x => { - const ret = typeof x === 'string' ? parseCookiePhantomjs(x) : x; - - if (!ret.domain) { - ret.domain = parsedUrl.hostname; - } +const fileUrl = require('file-url'); +const isUrl = require('is-url-superb'); +const puppeteer = require('puppeteer'); +const toughCookie = require('tough-cookie'); + +const parseCookie = cookie => { + if (typeof cookie === 'object') { + return cookie; + } + + const ret = toughCookie.parse(cookie).toJSON(); + ret.name = ret.key; + return ret; +}; - if (!ret.path) { - ret.path = parsedUrl.path; - } +const hideElement = element => { + element.style.visibility = 'hidden'; +}; - return ret; - }); +const getBoundingClientRect = element => { + const {height, width, x, y} = element.getBoundingClientRect(); + return {height, width, x, y}; }; -module.exports = (url, size, opts) => { +module.exports = async (url, opts) => { opts = Object.assign({ - delay: 0, - scale: 1, - format: 'png' + cookies: [], + fullPage: true, + hide: [], + width: 1920, + height: 1080 }, opts); - const args = Object.assign(opts, { - url, - width: size.split(/x/i)[0] * opts.scale, - height: size.split(/x/i)[1] * opts.scale, - cookies: handleCookies(opts.cookies, url), - format: opts.format === 'jpg' ? 'jpeg' : opts.format, - css: /\.css$/.test(opts.css) ? fs.readFileSync(opts.css, 'utf8') : opts.css, - script: /\.js$/.test(opts.script) ? fs.readFileSync(opts.script, 'utf8') : opts.script - }); + const uri = isUrl(url) ? url : fileUrl(url); + const { + cookies, crop, format, headers, height, hide, keepAlive, password, scale, + script, selector, style, timeout, transparent, userAgent, username, width + } = opts; - const cp = phantomBridge(path.join(__dirname, 'stream.js'), [ - '--ignore-ssl-errors=true', - '--local-to-remote-url-access=true', - '--ssl-protocol=any', - JSON.stringify(args) - ]); + opts.type = format === 'jpg' ? 'jpeg' : format; - const stream = base64Stream.decode(); + if (crop) { + opts.fullPage = false; + } - process.stderr.setMaxListeners(0); + if (timeout) { + opts.timeout = timeout * 1000; + } - cp.stderr.setEncoding('utf8'); - cp.stdout.pipe(stream); + if (transparent) { + opts.omitBackground = true; + } - byline(cp.stderr).on('data', data => { - data = data.trim(); + const browser = opts.browser || await puppeteer.launch(); + const page = await browser.newPage(); + const viewport = { + height, + width, + deviceScaleFactor: typeof scale === 'number' ? scale : null + }; - if (/ phantomjs\[/.test(data)) { - return; - } + if (username && password) { + await page.authenticate({username, password}); + } - if (/http:\/\/requirejs.org\/docs\/errors.html#mismatch/.test(data)) { - return; - } + if (Array.isArray(cookies) && cookies.length > 0) { + await Promise.all(cookies.map(x => page.setCookie(parseCookie(x)))); + } - if (data.startsWith('WARN: ')) { - stream.emit('warning', data.replace(/^WARN: /, '')); - stream.emit('warn', data.replace(/^WARN: /, '')); // TODO: deprecate this event in v5 - return; - } + if (typeof headers === 'object') { + await page.setExtraHTTPHeaders(headers); + } + + if (userAgent) { + await page.setUserAgent(userAgent); + } + + await page.setViewport(viewport); + await page.goto(uri, opts); - if (data.length > 0) { - const err = new Error(data); - err.noStack = true; - cp.stdout.unpipe(stream); - stream.emit('error', err); + if (script) { + const key = isUrl(script) ? 'url' : script.endsWith('.js') ? 'path' : 'content'; + await page.addScriptTag({[key]: script}); + } + + if (style) { + const key = isUrl(style) ? 'url' : style.endsWith('.css') ? 'path' : 'content'; + await page.addStyleTag({[key]: style}); + + if (isUrl(style)) { + console.log(key, style); } - }); + } + + if (Array.isArray(hide) && hide.length > 0) { + await Promise.all(hide.map(x => page.$eval(x, hideElement))); + } + + if (selector) { + await page.waitForSelector(selector, {visible: true}); - return stream; + opts.clip = await page.$eval(selector, getBoundingClientRect); + opts.fullPage = false; + } + + const buf = await page.screenshot(opts); + await page.close(); + + if (keepAlive !== true) { + await browser.close(); + } + + return buf; }; + +module.exports.startBrowser = puppeteer.launch; diff --git a/license b/license index a8ecbbe..db6bc32 100644 --- a/license +++ b/license @@ -1,21 +1,9 @@ -The MIT License (MIT) +MIT License -Copyright (c) Kevin Mårtensson +Copyright (c) Kevin Mårtensson (github.com/kevva) -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/package.json b/package.json index ab3ea78..bca249c 100644 --- a/package.json +++ b/package.json @@ -1,67 +1,51 @@ { - "name": "screenshot-stream", - "version": "4.2.0", - "description": "Capture screenshot of a website and return it as a stream", - "license": "MIT", - "repository": "kevva/screenshot-stream", - "author": { - "name": "Kevin Mårtensson", - "email": "kevinmartensson@gmail.com", - "url": "github.com/kevva" - }, - "engines": { - "node": ">=4" - }, - "scripts": { - "test": "xo && ava" - }, - "files": [ - "index.js", - "stream.js" - ], - "keywords": [ - "image", - "page", - "phantom", - "phantomjs", - "resolution", - "screen", - "screenshot", - "size", - "stream", - "url" - ], - "dependencies": { - "base64-stream": "^0.1.2", - "byline": "^4.2.1", - "object-assign": "^4.0.1", - "parse-cookie-phantomjs": "^1.0.0", - "phantom-bridge": "^2.0.0" - }, - "devDependencies": { - "ava": "*", - "cookie": "^0.3.1", - "get-port": "^3.1.0", - "get-stream": "^3.0.0", - "image-size": "^0.5.4", - "is-jpg": "^1.0.0", - "is-png": "^1.0.0", - "pify": "^3.0.0", - "png-js": "^0.1.1", - "xo": "*" - }, - "xo": { - "esnext": true, - "overrides": [ - { - "files": "stream.js", - "esnext": false, - "rules": { - "import/no-extraneous-dependencies": 0, - "import/no-unresolved": 0, - "no-multi-assign": 0 - } - } - ] - } + "name": "screenshot-stream", + "version": "4.2.0", + "description": "Capture screenshot of a website and return it as a stream", + "license": "MIT", + "repository": "kevva/screenshot-stream", + "author": { + "name": "Kevin Mårtensson", + "email": "kevinmartensson@gmail.com", + "url": "github.com/kevva" + }, + "engines": { + "node": ">=8" + }, + "scripts": { + "test": "xo && ava" + }, + "files": [ + "index.js" + ], + "keywords": [ + "image", + "page", + "phantom", + "phantomjs", + "resolution", + "screen", + "screenshot", + "size", + "stream", + "url" + ], + "dependencies": { + "file-url": "^2.0.2", + "is-url-superb": "^2.0.0", + "parse-resolution": "^1.0.0", + "puppeteer": "^0.12.0", + "tough-cookie": "^2.3.3" + }, + "devDependencies": { + "ava": "*", + "create-test-server": "^2.1.1", + "image-size": "^0.6.1", + "is-jpg": "^1.0.0", + "is-png": "^1.1.0", + "nock": "^9.0.22", + "pify": "^3.0.0", + "png-js": "^0.1.1", + "xo": "*" + } } diff --git a/readme.md b/readme.md index 7e7507e..b0b9abc 100644 --- a/readme.md +++ b/readme.md @@ -1,12 +1,12 @@ # screenshot-stream [![Build Status](https://travis-ci.org/kevva/screenshot-stream.svg?branch=master)](https://travis-ci.org/kevva/screenshot-stream) -> Capture screenshot of a website and return it as a stream +> Capture screenshot of a website ## Install ``` -$ npm install --save screenshot-stream +$ npm install screenshot-stream ``` @@ -16,9 +16,9 @@ $ npm install --save screenshot-stream const fs = require('fs'); const screenshot = require('screenshot-stream'); -const stream = screenshot('http://google.com', '1024x768', {crop: true}); - -stream.pipe(fs.createWriteStream('google.com-1024x768.png')); +screenshot('http://google.com', '1024x768', {crop: true}).then(data => { + fs.writeFileSync('google.com-1024x768.png', data); +}); ``` @@ -46,24 +46,17 @@ Define options to be used. ##### crop -Type: `Boolean`
+Type: `boolean`
Default: `false` Crop to the set height. -##### delay - -Type: `number` *(seconds)*
-Default: `0` - -Delay capturing the screenshot. Useful when the site does things after load that you want to capture. - ##### timeout Type: `number` *(seconds)*
Default: `60` -Number of seconds after which PhantomJS aborts the request. +Number of seconds after which Google Chrome aborts the request. ##### selector @@ -71,18 +64,6 @@ Type: `string` Capture a specific DOM element. -##### css - -Type: `string` - -Apply custom CSS to the webpage. Specify some CSS or the path to a CSS file. - -##### script - -Type: `string` - -Apply custom JavaScript to the webpage. Specify some JavaScript or the path to a file. - ##### hide Type: `Array` @@ -98,7 +79,7 @@ Set custom headers. ##### cookies -Type: `Array` or `Object` +Type: `Array` `Object` A string with the same format as a [browser cookie](http://en.wikipedia.org/wiki/HTTP_cookie) or an object of what [`phantomjs.addCookie`](http://phantomjs.org/api/phantom/method/add-cookie.html) accepts. @@ -136,27 +117,15 @@ Set a custom user agent. ##### transparent -Type: `Boolean`
+Type: `boolean`
Default: `false` Set background color to `transparent` instead of `white` if no background is set. -#### .on('error', callback) - -Type: `Function` - -PhantomJS errors. - -#### .on('warning', callback) - -Type: `Function` - -Warnings with for example page errors. - ## CLI -See the [pageres](https://github.com/sindresorhus/pageres#usage) CLI. +See the [Pageres CLI](https://github.com/sindresorhus/pageres-cli). ## License diff --git a/stream.js b/stream.js deleted file mode 100644 index 11434b1..0000000 --- a/stream.js +++ /dev/null @@ -1,127 +0,0 @@ -/* global phantom, document, window, btoa */ -'use strict'; -var system = require('system'); -var page = require('webpage').create(); -var objectAssign = require('object-assign'); - -var opts = JSON.parse(system.args[1]); -var log = console.log; - -function formatTrace(trace) { - var src = trace.file || trace.sourceURL; - var fn = (trace.function ? ' in function ' + trace.function : ''); - return ' → ' + src + ' on line ' + trace.line + fn; -} - -console.log = console.error = function () { - system.stderr.writeLine([].slice.call(arguments).join(' ')); -}; - -if (opts.username && opts.password) { - opts.headers = objectAssign({}, opts.headers, { - Authorization: 'Basic ' + btoa(opts.username + ':' + opts.password) - }); -} - -if (opts.userAgent) { - page.settings.userAgent = opts.userAgent; -} - -page.settings.resourceTimeout = (opts.timeout || 60) * 1000; - -phantom.cookies = opts.cookies; - -phantom.onError = function (err, trace) { - err = err.replace(/\n/g, ''); - console.error('PHANTOM ERROR: ' + err + formatTrace(trace[0])); - phantom.exit(1); -}; - -page.onError = function (err, trace) { - err = err.replace(/\n/g, ''); - console.error('WARN: ' + err + formatTrace(trace[0])); -}; - -page.onResourceError = function (resourceError) { - console.error('WARN: Unable to load resource #' + resourceError.id + ' (' + resourceError.errorString + ') → ' + resourceError.url); -}; - -page.onResourceTimeout = function (resourceTimeout) { - console.error('Resource timed out #' + resourceTimeout.id + ' (' + resourceTimeout.errorString + ') → ' + resourceTimeout.url); - phantom.exit(1); -}; - -page.viewportSize = { - width: opts.width, - height: opts.height -}; - -page.customHeaders = opts.headers || {}; -page.zoomFactor = opts.scale; - -page.open(opts.url, function (status) { - if (status === 'fail') { - console.error('Couldn\'t load url: ' + opts.url); - phantom.exit(1); - return; - } - - if (opts.crop) { - page.clipRect = { - top: 0, - left: 0, - width: opts.width, - height: opts.height - }; - } - - page.evaluate(function (css, transparent) { - var bgColor = window - .getComputedStyle(document.body) - .getPropertyValue('background-color'); - - if (!bgColor || bgColor === 'rgba(0, 0, 0, 0)') { - document.body.style.backgroundColor = transparent ? 'transparent' : 'white'; - } - - if (css) { - var el = document.createElement('style'); - el.appendChild(document.createTextNode(css)); - document.head.appendChild(el); - } - }, opts.css, opts.transparent); - - window.setTimeout(function () { - if (opts.hide) { - page.evaluate(function (els) { - els.forEach(function (el) { - [].forEach.call(document.querySelectorAll(el), function (e) { - e.style.visibility = 'hidden'; - }); - }); - }, opts.hide); - } - - if (opts.selector) { - var clipRect = page.evaluate(function (el) { - return document - .querySelector(el) - .getBoundingClientRect(); - }, opts.selector); - - clipRect.height *= page.zoomFactor; - clipRect.width *= page.zoomFactor; - clipRect.top *= page.zoomFactor; - clipRect.left *= page.zoomFactor; - - page.clipRect = clipRect; - } - - if (opts.script) { - page.evaluateJavaScript('function () { ' + opts.script + '}'); - } - - log.call(console, page.renderBase64(opts.format)); - phantom.exit(); - }, opts.delay * 1000); -}); diff --git a/test.js b/test.js index 54c5171..562387c 100644 --- a/test.js +++ b/test.js @@ -3,190 +3,255 @@ import test from 'ava'; import imageSize from 'image-size'; import isJpg from 'is-jpg'; import isPng from 'is-png'; -import PNG from 'png-js'; -import getStream from 'get-stream'; +import nock from 'nock'; import pify from 'pify'; -import server from './fixtures/server'; -import m from '.'; +import PNG from 'png-js'; +import createServer from './fixtures/server'; +import screenshotStream, {startBrowser} from '.'; + +let browser; +let m; +let server; + +test.before(async () => { + browser = await startBrowser(); + server = await createServer(); + + m = (url, opts) => screenshotStream(url, Object.assign({}, opts, { + browser, + keepAlive: true + })); + + nock('http://foo.bar') + .get('/script.js') + .replyWithFile(200, path.join(__dirname, 'fixtures', 'script.js')) + .get('/style.css') + .replyWithFile(200, path.join(__dirname, 'fixtures', 'style.css')); +}); + +test.after(async () => { + await browser.close(); + await server.close(); +}); test('generate screenshot', async t => { - const stream = m('http://yeoman.io', '1024x768'); - t.true(isPng(await getStream.buffer(stream))); + t.true(isPng(await m(server.url, { + width: 100, + height: 100 + }))); }); test('crop image using the `crop` option', async t => { - const stream = m('http://yeoman.io', '1024x768', {crop: true}); - const size = imageSize(await getStream.buffer(stream)); + const size = imageSize(await m(server.url, { + width: 1024, + height: 768, + crop: true + })); + t.is(size.width, 1024); t.is(size.height, 768); }); test('capture a DOM element using the `selector` option', async t => { - const stream = m('http://yeoman.io', '1024x768', { - selector: '.page-header' - }); - - const size = imageSize(await getStream.buffer(stream)); - t.is(size.width, 1024); - t.is(size.height, 80); + const size = imageSize(await m(server.url, { + width: 1024, + height: 768, + selector: 'div' + })); + + t.is(size.width, 100); + t.is(size.height, 100); }); -test('capture a DOM element using the `selector` option only after delay', async t => { - const fixture = path.join(__dirname, 'fixtures', 'test-delay-element.html'); - const stream = m(fixture, '1024x768', { - selector: 'div', - delay: 5 - }); +test('wait for DOM element when using the `selector` option', async t => { + const size = imageSize(await m(`${server.url}/delay`, { + width: 1024, + height: 768, + selector: 'div' + })); - const size = imageSize(await getStream.buffer(stream)); - t.is(size.width, 300); - t.is(size.height, 200); + t.is(size.width, 100); + t.is(size.height, 100); }); test('hide elements using the `hide` option', async t => { - const fixture = path.join(__dirname, 'fixtures', 'test-hide-element.html'); - const stream = m(fixture, '100x100', {hide: ['div']}); - const png = new PNG(await getStream.buffer(stream)); + const png = new PNG(await m(server.url, { + width: 100, + height: 100, + hide: ['div'] + })); + const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); t.is(pixels[0], 255); }); -test('ignore multiline page errors', async t => { - const fixture = path.join(__dirname, 'fixtures', 'test-error-script.html'); - const stream = m(fixture, '100x100'); - t.true(isPng(await getStream.buffer(stream))); -}); - test('auth using the `username` and `password` options', async t => { - const stream = m('http://httpbin.org/basic-auth/user/passwd', '1024x768', { + t.true(isPng(await m('http://httpbin.org/basic-auth/user/passwd', { + width: 100, + height: 100, username: 'user', password: 'passwd' - }); - - const data = await pify(stream.once.bind(stream), {errorFirst: false})('data'); - t.truthy(data.length); + }))); }); -test('have a `delay` option', async t => { - const now = new Date(); - const stream = m('http://yeoman.io', '1024x768', {delay: 2}); - await pify(stream.once.bind(stream), {errorFirst: false})('data'); - - t.true((new Date()) - now > 2000); -}); - -test('have a `dpi` option', async t => { - const stream = m('http://yeoman.io', '1024x768', { +test('have a `scale` option', async t => { + const size = imageSize(await m(server.url, { + width: 100, + height: 100, crop: true, scale: 2 - }); + })); - const size = imageSize(await getStream.buffer(stream)); - t.is(size.width, 1024 * 2); - t.is(size.height, 768 * 2); + t.is(size.width, 100 * 2); + t.is(size.height, 100 * 2); }); test('have a `format` option', async t => { - const stream = m('http://yeoman.io', '1024x768', {format: 'jpg'}); - t.true(isJpg(await getStream.buffer(stream))); + t.true(isJpg(await m(server.url, { + width: 100, + height: 100, + format: 'jpg' + }))); }); -test('have a `css` option', async t => { - const stream = m('http://yeoman.io', '1024x768', {css: '.mobile-bar { background-color: red !important; }'}); - const png = new PNG(await getStream.buffer(stream)); +test('`script` option inline', async t => { + const png = new PNG(await m(server.url, { + width: 100, + height: 100, + script: `document.querySelector('div').style.backgroundColor = 'red';` + })); + const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); + t.is(pixels[0], 255); t.is(pixels[1], 0); t.is(pixels[2], 0); }); -test('have a `css` file', async t => { - const stream = m('http://yeoman.io', '1024x768', {css: 'fixtures/style.css'}); - const png = new PNG(await getStream.buffer(stream)); +test('`script` option file', async t => { + const png = new PNG(await m(server.url, { + width: 100, + height: 100, + script: 'fixtures/script.js' + })); + const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); - t.is(pixels[0], 0); - t.is(pixels[1], 128); + + t.is(pixels[0], 255); + t.is(pixels[1], 0); + t.is(pixels[2], 0); +}); + +test.skip('`script` option url', async t => { // eslint-disable-line ava/no-skip-test + const png = new PNG(await m(server.url, { + width: 100, + height: 100, + script: 'http://foo.bar/script.js' + })); + + const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); + + t.is(pixels[0], 255); + t.is(pixels[1], 0); t.is(pixels[2], 0); }); -test('have a `script` option', async t => { - const stream = m('http://yeoman.io', '1024x768', {script: 'document.querySelector(\'.mobile-bar\').style.backgroundColor = \'red\';'}); - const png = new PNG(await getStream.buffer(stream)); +test('`style` option inline', async t => { + const png = new PNG(await m(server.url, { + width: 100, + height: 100, + style: 'div { background-color: red !important; }' + })); + const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); + t.is(pixels[0], 255); t.is(pixels[1], 0); t.is(pixels[2], 0); }); -test('have a `js` file', async t => { - const stream = m('http://yeoman.io', '1024x768', {script: 'fixtures/script.js'}); - const png = new PNG(await getStream.buffer(stream)); +test('`style` option file', async t => { + const png = new PNG(await m(server.url, { + width: 100, + height: 100, + style: 'fixtures/style.css' + })); + const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); - t.is(pixels[0], 0); - t.is(pixels[1], 128); + + t.is(pixels[0], 255); + t.is(pixels[1], 0); + t.is(pixels[2], 0); +}); + +test.skip('`style` option url', async t => { // eslint-disable-line ava/no-skip-test + const png = new PNG(await m(server.url, { + width: 100, + height: 100, + style: 'http://foo.bar/style.css' + })); + + console.log(png); + + const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); + + t.is(pixels[0], 255); + t.is(pixels[1], 0); t.is(pixels[2], 0); }); test('send cookie', async t => { - const s = await server(); - const stream = m(`${s.url}/cookies`, '100x100', { + const png = new PNG(await m(`${server.url}/cookie`, { + width: 100, + height: 100, cookies: ['color=black; Path=/; Domain=localhost'] - }); + })); - const png = new PNG(await getStream.buffer(stream)); const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); - t.is(pixels[0], 0); - await s.close(); }); test('send cookie using an object', async t => { - const s = await server(); - const stream = m(`${s.url}/cookies`, '100x100', { + const png = new PNG(await m(`${server.url}/cookie`, { + width: 100, + height: 100, cookies: [{ name: 'color', value: 'black', domain: 'localhost' }] - }); + })); - const png = new PNG(await getStream.buffer(stream)); const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); - t.is(pixels[0], 0); - await s.close(); }); -test('send headers', async t => { - const s = await server(); - - m(`${s.url}`, '100x100', { +test.skip('send headers', async t => { // eslint-disable-line ava/no-skip-test + await m(`${server.url}`, { + width: 100, + height: 100, headers: { foobar: 'unicorn' } }); - t.is((await pify(s.once.bind(s), {errorFirst: false})('/')).headers.foobar, 'unicorn'); - await s.close(); + t.is((await pify(server.once.bind(server), {errorFirst: false})('/headers')).headers.foobar, 'unicorn'); }); test('handle redirects', async t => { - const s = await server(); - const stream = m(`${s.url}/redirect`, '100x100'); - const png = new PNG(await getStream.buffer(stream)); + const png = new PNG(await m(`${server.url}/redirect`, { + width: 100, + height: 100 + })); + const pixels = await pify(png.decode.bind(png), {errorFirst: false})(); t.is(pixels[0], 0); - await s.close(); }); test('resource timeout', async t => { - const s = await server({delay: 5}); - const stream = m(s.url, '100x100', {timeout: 1}); - - await Promise.race([ - pify(s.once.bind(s), {errorFirst: false})('/').then(() => t.fail('Expected resource timed out error')), - t.throws(getStream(stream), `Resource timed out #1 (Network timeout on resource.) → ${s.url}/`) - ]); - - await s.close(); + await t.throws(m(`${server.url}/timeout/5`, { + width: 100, + height: 100, + timeout: 1 + }), /1000ms exceeded/); });