From 56a475f86bba60466299e05cf95ccf14b74fafd9 Mon Sep 17 00:00:00 2001 From: Frankie Bagnardi Date: Fri, 23 Feb 2018 15:13:08 -0700 Subject: [PATCH] feat: elHandle:screenshot captures full element (#1787) feat: make ElementHandle.screenshot work with large elements This patch increases the viewport size if the element is bigger than viewport. Fixes #1779 --- lib/ElementHandle.js | 49 ++++++-- ...creenshot-element-larger-than-viewport.png | Bin 0 -> 1274 bytes ...reenshot-element-with-scroll-container.png | Bin 0 -> 2521 bytes test/test.js | 109 +++++++++++++++++- 4 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 test/golden/screenshot-element-larger-than-viewport.png create mode 100644 test/golden/screenshot-element-with-scroll-container.png diff --git a/lib/ElementHandle.js b/lib/ElementHandle.js index 2257daf4fc0..e600236dff3 100644 --- a/lib/ElementHandle.js +++ b/lib/ElementHandle.js @@ -58,9 +58,7 @@ class ElementHandle extends JSHandle { */ async _visibleCenter() { await this._scrollIntoViewIfNeeded(); - const box = await this.boundingBox(); - if (!box) - throw new Error('Node is not visible'); + const box = await this._assertBoundingBox(); return { x: box.x + box.width / 2, y: box.y + box.height / 2 @@ -137,24 +135,59 @@ class ElementHandle extends JSHandle { return {x, y, width, height}; } + /** + * @return {!Promise} + */ + async _assertBoundingBox() { + const boundingBox = await this.boundingBox(); + if (boundingBox) + return boundingBox; + + throw new Error('Node is either not visible or not an HTMLElement'); + } + /** * * @param {!Object=} options * @returns {!Promise} */ async screenshot(options = {}) { - await this._scrollIntoViewIfNeeded(); + let needsViewportReset = false; + + let boundingBox = await this._assertBoundingBox(); + + const viewport = this._page.viewport(); + + if (boundingBox.width > viewport.width || boundingBox.height > viewport.height) { + const newViewport = { + width: Math.max(viewport.width, Math.ceil(boundingBox.width)), + height: Math.max(viewport.height, Math.ceil(boundingBox.height)), + }; + await this._page.setViewport(Object.assign({}, viewport, newViewport)); + + needsViewportReset = true; + } + + await this.executionContext().evaluate(function(element) { + element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'}); + }, this); + + boundingBox = await this._assertBoundingBox(); + const { layoutViewport: { pageX, pageY } } = await this._client.send('Page.getLayoutMetrics'); - const boundingBox = await this.boundingBox(); - if (!boundingBox) - throw new Error('Node is not visible'); const clip = Object.assign({}, boundingBox); clip.x += pageX; clip.y += pageY; - return await this._page.screenshot(Object.assign({}, { + + const imageData = await this._page.screenshot(Object.assign({}, { clip }, options)); + + if (needsViewportReset) + await this._page.setViewport(viewport); + + return imageData; } /** diff --git a/test/golden/screenshot-element-larger-than-viewport.png b/test/golden/screenshot-element-larger-than-viewport.png new file mode 100644 index 0000000000000000000000000000000000000000..4842968bd9c457da894945288c12ca0eda2c130b GIT binary patch literal 1274 zcmeAS@N?(olHy`uVBq!ia0y~yV2S~^1jKr#RT literal 0 HcmV?d00001 diff --git a/test/golden/screenshot-element-with-scroll-container.png b/test/golden/screenshot-element-with-scroll-container.png new file mode 100644 index 0000000000000000000000000000000000000000..c7860b32a28af62c06500082a9d7bde0deb5da1c GIT binary patch literal 2521 zcmeAS@N?(olHy`uVBq!ia0y~yVDbQA4mP03EIH}*3=EtLJY5_^D(1Ysv61(%0}qSi zse=2PwCY*gWOJC;C#WS=JZw6CCW@Ki!2Rs+3=9p+EC2U>{`2MzBg2NfrM1;3*f|*# zScDiHI8+!K1b}8MPGDegQeb3gabRNLXk=j!U>a308V;lBVKiTimJg%l button.style.display = 'none', button); const error = await button.click().catch(err => err); - expect(error.message).toBe('Node is not visible'); + expect(error.message).toBe('Node is either not visible or not an HTMLElement'); }); it('should throw for recursively hidden nodes', async({page, server}) => { await page.goto(server.PREFIX + '/input/button.html'); const button = await page.$('button'); await page.evaluate(button => button.parentElement.style.display = 'none', button); const error = await button.click().catch(err => err); - expect(error.message).toBe('Node is not visible'); + expect(error.message).toBe('Node is either not visible or not an HTMLElement'); }); it('should throw for
elements', async({page, server}) => { await page.setContent('hello
goodbye'); const br = await page.$('br'); const error = await br.click().catch(err => err); - expect(error.message).toBe('Node is not visible'); + expect(error.message).toBe('Node is either not visible or not an HTMLElement'); }); }); @@ -2124,6 +2124,107 @@ describe('Page', function() { const screenshot = await elementHandle.screenshot(); expect(screenshot).toBeGolden('screenshot-element-padding-border.png'); }); + it('should capture full element when larger than viewport', async({page, server}) => { + // compare with .to-screenshot size + await page.setViewport({width: 500, height: 500}); + + await page.setContent(` + something above + +
+
+
+ `); + + await page.evaluate(function() { + window.scrollTo(11, 12); + }); + + const elementHandle = await page.$('div.to-screenshot'); + const screenshot = await elementHandle.screenshot(); + expect(screenshot).toBeGolden('screenshot-element-larger-than-viewport.png'); + + expect(await page.evaluate(function() { + return { w: window.innerWidth, h: window.innerHeight }; + })).toEqual({ w: 500, h: 500 }); + }); + it('should screenshot element with scroll container', async({page, server}) => { + // compare with .to-screenshot size + await page.setViewport({width: 500, height: 500}); + + await page.setContent(` + something above + +
+
+
+
+
+
+
+ `); + + await page.evaluate(function() { + window.scrollTo(11, 12); + }); + + await page.$eval('div.container1', function(element) { + element.scrollTo(100, 0); + }); + + await page.$eval('div.container2', function(element) { + element.scrollTo(10, 30); + }); + + const elementHandle = await page.$('div.to-screenshot'); + const screenshot = await elementHandle.screenshot(); + expect(screenshot).toBeGolden('screenshot-element-with-scroll-container.png'); + }); it('should scroll element into view', async({page, server}) => { await page.setViewport({width: 500, height: 500}); await page.setContent(` @@ -2165,7 +2266,7 @@ describe('Page', function() { const elementHandle = await page.$('h1'); await page.evaluate(element => element.remove(), elementHandle); const screenshotError = await elementHandle.screenshot().catch(error => error); - expect(screenshotError.message).toBe('Node is detached from document'); + expect(screenshotError.message).toBe('Node is either not visible or not an HTMLElement'); }); });