mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
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
This commit is contained in:
parent
b07e705dc9
commit
56a475f86b
@ -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<?{x: number, y: number, width: number, height: number}>}
|
||||
*/
|
||||
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<Object>}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
BIN
test/golden/screenshot-element-larger-than-viewport.png
Normal file
BIN
test/golden/screenshot-element-larger-than-viewport.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
test/golden/screenshot-element-with-scroll-container.png
Normal file
BIN
test/golden/screenshot-element-with-scroll-container.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
109
test/test.js
109
test/test.js
@ -2072,20 +2072,20 @@ describe('Page', function() {
|
||||
const button = await page.$('button');
|
||||
await page.evaluate(button => 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 <br> elements', async({page, server}) => {
|
||||
await page.setContent('hello<br>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
|
||||
<style>div.spacer {
|
||||
border: 2px solid red;
|
||||
background: red;
|
||||
height: 600px;
|
||||
width: 52px;
|
||||
}
|
||||
div.to-screenshot {
|
||||
border: 2px solid blue;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
width: 600px;
|
||||
height: 200.5px;
|
||||
margin-left: 50px;
|
||||
transform: scaleY(1.2);
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<div class="spacer"></div>
|
||||
<div class="to-screenshot"></div>
|
||||
<div class="spacer"></div>
|
||||
`);
|
||||
|
||||
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
|
||||
<style>div.spacer {
|
||||
border: 2px solid red;
|
||||
background: red;
|
||||
height: 600px;
|
||||
width: 52px;
|
||||
}
|
||||
div.container1 {
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
div.container2 {
|
||||
width: 620px;
|
||||
height: 620px;
|
||||
overflow: auto;
|
||||
}
|
||||
div.to-screenshot {
|
||||
border: 2px solid blue;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
width: 580px;
|
||||
height: 580px;
|
||||
margin-top: 50px;
|
||||
margin-left: 200px;
|
||||
margin-right: 50px;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<div class="spacer"></div>
|
||||
<div class="container1">
|
||||
<div class="container2">
|
||||
<div class="to-screenshot"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
`);
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user