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() {
|
async _visibleCenter() {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const box = await this.boundingBox();
|
const box = await this._assertBoundingBox();
|
||||||
if (!box)
|
|
||||||
throw new Error('Node is not visible');
|
|
||||||
return {
|
return {
|
||||||
x: box.x + box.width / 2,
|
x: box.x + box.width / 2,
|
||||||
y: box.y + box.height / 2
|
y: box.y + box.height / 2
|
||||||
@ -137,24 +135,59 @@ class ElementHandle extends JSHandle {
|
|||||||
return {x, y, width, height};
|
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
|
* @param {!Object=} options
|
||||||
* @returns {!Promise<Object>}
|
* @returns {!Promise<Object>}
|
||||||
*/
|
*/
|
||||||
async screenshot(options = {}) {
|
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 { 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);
|
const clip = Object.assign({}, boundingBox);
|
||||||
clip.x += pageX;
|
clip.x += pageX;
|
||||||
clip.y += pageY;
|
clip.y += pageY;
|
||||||
return await this._page.screenshot(Object.assign({}, {
|
|
||||||
|
const imageData = await this._page.screenshot(Object.assign({}, {
|
||||||
clip
|
clip
|
||||||
}, options));
|
}, 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');
|
const button = await page.$('button');
|
||||||
await page.evaluate(button => button.style.display = 'none', button);
|
await page.evaluate(button => button.style.display = 'none', button);
|
||||||
const error = await button.click().catch(err => err);
|
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}) => {
|
it('should throw for recursively hidden nodes', async({page, server}) => {
|
||||||
await page.goto(server.PREFIX + '/input/button.html');
|
await page.goto(server.PREFIX + '/input/button.html');
|
||||||
const button = await page.$('button');
|
const button = await page.$('button');
|
||||||
await page.evaluate(button => button.parentElement.style.display = 'none', button);
|
await page.evaluate(button => button.parentElement.style.display = 'none', button);
|
||||||
const error = await button.click().catch(err => err);
|
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}) => {
|
it('should throw for <br> elements', async({page, server}) => {
|
||||||
await page.setContent('hello<br>goodbye');
|
await page.setContent('hello<br>goodbye');
|
||||||
const br = await page.$('br');
|
const br = await page.$('br');
|
||||||
const error = await br.click().catch(err => err);
|
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();
|
const screenshot = await elementHandle.screenshot();
|
||||||
expect(screenshot).toBeGolden('screenshot-element-padding-border.png');
|
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}) => {
|
it('should scroll element into view', async({page, server}) => {
|
||||||
await page.setViewport({width: 500, height: 500});
|
await page.setViewport({width: 500, height: 500});
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
@ -2165,7 +2266,7 @@ describe('Page', function() {
|
|||||||
const elementHandle = await page.$('h1');
|
const elementHandle = await page.$('h1');
|
||||||
await page.evaluate(element => element.remove(), elementHandle);
|
await page.evaluate(element => element.remove(), elementHandle);
|
||||||
const screenshotError = await elementHandle.screenshot().catch(error => error);
|
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