-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reliable Screenshots (resizeWindow and SELENIUM_RESIZE_OFFSET_HEIGHT) #47
base: master
Are you sure you want to change the base?
Conversation
Hi thanks for the contribution. Can you describe what happens when you try to do it when running in a real browser, including your setup, and the command that leads to it not working? |
Hi, there was a comment of yours in the sources of The size of the View Port (window.innerWidth and window.innerHeight) is the size of the window minus the window chrome (frame, browser-bars etc.). When you use The TestCafe browser tools solve the same problem. I took that algorithm (even reused the variable names) and used that implementation here. |
I haven't had a chance to test the code. Guess I was interested in what the actuall result of the existing code was when run in a real window, like if you had some sample code and a screenshot for example. My other thought is, has this problem been identified before with selenium? I had a quick search but couldn't find anything which seems unusal. Based on your description, I would have thought such an issue would have affected others... Because essentially what you are saying is Also, I'm not keen on the breaking change. I think there is a more elegant solution to "overload" the method so that it doesn't break existing code. There are a couple ways to do it, one way is..
Additionally is there a way for the code to know if it is being run headless or not and use the new code for normal browsers and existing code for headless operation? |
Hi, so I created a repository wit numerous configurations: https://github.com/htho/testcafe-browser-provider-selenium-repro-screenshot Please note that I was unable to test an old Chrome browser. |
Thanks for the repo, it helped frame the issue. I spent some time investigating it. To summarise the issue.
Explanation
Example code for native Testcafe. There is a bug in your code in the use of import { ClientFunction } from 'testcafe';
const { PNG } = require( "pngjs");
const { readFileSync } = require('fs');
const targetWidth = 800;
const targetHeight = 600;
const getWindowDimension = ClientFunction(() => {
return {
width: window.innerWidth,
height: window.innerHeight
};
});
export async function getScreenSize() {
const fn = ClientFunction(() => {
return {
width: $(window).width(),
height: $(window).height()
};
});
return fn();
}
fixture `TestController.resizeWindow`
.page `https://devexpress.github.io/testcafe/example/`
.beforeEach(async t => {
// set size to something else so resize is necessary
await t.resizeWindow(targetWidth+200, targetHeight+200);
});
// This passes, but it is not doing what you expect, it is a false positive!
// The window.innerHeight and window.innerWidth actually return the height and width of the window, not the viewport.
// When you run this test you will see that they resize to the same size on the screen as the other tests that fail.
// Windowed: Expected to Pass as it checks window size
// Headless: Expected to Pass as it checks window size
test('check window.innerHeight/innerWidth is equal to window size', async t => {
await t.resizeWindow(targetWidth, targetHeight);
let { width, height } = await getWindowDimension();
console.log(width, height);
await t.expect([width, height]).eql([targetWidth, targetHeight]);
});
// This will fail as it checks the real viewport size with jquery. The size matches the screenshot size.
// Windowed: Expected to fail as viewport (783x583px) is not equal to window size
// Headless: Expected to fail as viewport (783x583px) is not equal to window size
test('check resize viewport is equal to window size with jquery', async t => {
await t.resizeWindow(targetWidth, targetHeight);
let { width, height } = await getScreenSize();
console.log(width, height);
await t.expect([width, height]).eql([targetWidth, targetHeight]);
});
// This will fail as viewport != window size
// Windowed: Expected to fail as screenshot (783x583px) is not equal to window size
// Headless: Expected to fail as screenshot (783x583px) is not equal to window size
test('resize screenshot size is equal to window size', async t => {
await t.resizeWindow(targetWidth, targetHeight);
await t.takeScreenshot({path: 'resize-window.png'});
const png = PNG.sync.read(readFileSync('./screenshots/resize-window.png'));
await t.expect([png.width, png.height]).eql([targetWidth, targetHeight]);
}); Example code for native Selenium. const { PNG } = require( "pngjs");
const { readFileSync } = require('fs');
let fs = require('fs');
const assert = require("assert");
const { Builder, By, until } = require("selenium-webdriver");
const {Options} = require("selenium-webdriver/chrome.js");
const targetHeight = 600;
const targetWidth = 800;
const GRID_HOST = 'http://sundari.local:4444/wd/hub';
const npmRunScript = process.env.npm_lifecycle_event ?? fail("test not run via npm run!");
const allowedChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
function mkValidPath(path) {
return path.split("").map(char => allowedChars.includes(char) ? char : "_").join("");
}
describe("Test resizing", function() {
let driver;
const options = new Options();
beforeEach(async function() {
driver = await new Builder().usingServer(GRID_HOST).forBrowser("chrome").setChromeOptions(options.addArguments('--headless')).build();
// driver = await new Builder().usingServer(GRID_HOST).forBrowser("chrome").build();
// driver = await new Builder().usingServer(GRID_HOST).forBrowser("firefox").build();
// driver = await new Builder().usingServer(GRID_HOST).forBrowser("MicrosoftEdge").build();
await driver.get("about:blank");
await driver.manage().window().setRect({width: targetWidth+200, height: targetHeight+200});
});
afterEach(async function() {
await driver.quit();
});
// Note, this checks the actual window size, not the viewport size.
// Windowed: Expected to pass as window size is 800x600.
// Headless: Expected to pass as window size is 800x600.
it("check physical window size is what it is set to", async function() {
let rect = await driver.manage().window().getRect()
console.log(rect)
assert.notEqual(rect.width, targetWidth, "Width not expected to be targetWidth yet!");
assert.notEqual(rect.height, targetHeight, "Height not expected to be targetHeight yet!");
await driver.manage().window().setRect({width: targetWidth, height: targetHeight});
rect = await driver.manage().window().getRect()
console.log(rect)
assert.deepEqual([rect.height, rect.width], [targetHeight, targetWidth], "Window size is wrong.");
});
// Windowed: Expected to fail as screenshot size is 784x462 px compared to a window size of 800x600.
// Headless: Expected to pass as screenshot size is 800x600 px compared to a window size of 800x600.
it("check screenshot size, ie actual viewport size", async function() {
let initialSize = await driver.manage().window().getRect()
console.log(initialSize)
await driver.manage().window().setRect({width: targetWidth, height: targetHeight});
let newSize = await driver.manage().window().getRect()
console.log(newSize)
assert.notEqual(newSize.width, initialSize.width, "Width did not change!");
assert.notEqual(newSize.height, initialSize.height, "Height did not change!");
const screenshotPath = `./screenshots/${mkValidPath(npmRunScript)}--${mkValidPath(this.test.title)}.png`;
let encodedString = await driver.takeScreenshot();
await fs.writeFileSync(screenshotPath, encodedString, 'base64');
const png = PNG.sync.read(readFileSync(screenshotPath));
assert.deepEqual([png.height, png.width], [targetHeight, targetWidth], "Screenshot does not have the same number of pixels as the window!");
});
}); ConclusionSo in final conclusion,
If you want to have a set viewport, then I suggest implementing your own method to |
Thanks for the effort you made. Before we continues this discussion, we must make sure we are on the same page. (1) For me the most important requirement is, that the results of the plugin should be exactly the same as with testcafes built-in browser-providers (which use testcafe-browser-tools). (2) I am not sure about the wording you use. According to MDN The Viewport is the part of the document you're viewing which is currently visible in its window. (3) I can observe that for native testcafe browsers, when the size is set using While TestCafes documentation is ambigous about the exact meaning of (4) We can agree, that the name Can we agree on these 4 points? |
Somewhat agree. I say somewhat, because I don't think its 100% possible. (see point 2 from my conclusion above, ie not possible for headless selenium)
Yes, it is essentially the size of the screenshot, ie. the visible part of the web page within the browser windw.
No. This is not the case. Please see my code above for native Testcafe that demonstrates this.
When you watch the test run, you will see the windows in each test resize to the same physical size on the screen, thus you would expect all tests to have the same final resolution, but they don't. Therefore the first test case is wrong, and the other tests prove that TestCafe This is how testing should work. You evaluate what screen resolution most of your users use, and you test for that screen resolution, which would result in having a smaller viewport within a browser that has had its window set to that size.
Given the above, the documentation seems to be inconsistent. It seems odd that they would require it to work one way with native Testcafe and a different way with a browser provider plugin. Regardless, the actual Testcafe functionality supports the fact that resizeWindow() is referring to the physical browser windows, so I maintain the browser provider plugin documentation is wrong.
|
Hi, I don't have much time today, but I want to share some results with you: https://github.com/htho/testcafe-repro-screenshot-height Notice how innerWidth/innerHeight always passes, regardless of content and scrollbars. For our purpose of resizing the window to fit the viewport - the scrollbars should be taken into account. Scrollbars are 13px wide. Although jQuerys docucumentation claims it: |
Hi,
screenshots are unreliable, because resizeWindow is unreliable.
t.resizeWindow
directly uses SeleniumsWindow.setRect()
method, which worked fine for headless mode, but not when used in windowed mode.This should be fixed now.
But somehow the screenshots I took were 1px smaller than the size set using resizeWindow.
To workaround this, I added SELENIUM_RESIZE_OFFSET_HEIGHT which allows me to set this offset for selenium instead of inside my test code.
I hope you like what I did, and am looking forward to your feedback, which I am happy to apply to my PR.