Closed Bug 1471265 Opened 8 years ago Closed 7 years ago

WebDriver:TakeScreenshot fails in canvas "scale()" for huge web pages

Categories

(Remote Protocol :: Marionette, defect, P3)

59 Branch
defect

Tracking

(Not tracked)

RESOLVED DUPLICATE of bug 1485730

People

(Reporter: aclvaz, Unassigned)

References

()

Details

User Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36 Steps to reproduce: GeckoDriver Version: 0.21.0 Platform: Windows 7 Enterprise Edition 64 bits Firefox: 59.0.2 (64-bit) Selenium: 3.12.1 Testcase just load page from www.lwb.com or www.fitu.com and get a snapshot The following code resulted from the need to deal with multiple issues when getting a random web page snapshot (pop ups, alert windows, pages that load several different resources, have incomplete loads, make redirects, etc), coming from Selenium 2 and using FireFox Webdriver (Firefox 45) **that could take a FULL window snapshot and not only the ViewPort**. www.lwb.com, for instance, would get a snapshot with 1000 x 32766 px at 24 bits. With Selenium 3 and Gecko Driver/Marionette one can only get the viewport (most of the time) and pages with big heights result in a Javascript error that propagates upwards thru the stack trace and is not easy to spot unless browsing thru the source code (at least on C# bindings). I'm including the main code verbatim as a guideline to others that may face similar issues when taking snapshots. private void CreateWebDriver() { FirefoxProfile customProfile = new FirefoxProfile(); customProfile.SetPreference("dom.disable_beforeunload", true); // disable want to leave page question. customProfile.SetPreference("full-screen-api.approval-required", false); // disable maximize firefox question customProfile.SetPreference("network.http.connection-timeout", 25); // connection time-out. customProfile.SetPreference("dom.max_chrome_script_run_time", 0); customProfile.SetPreference("dom.max_script_run_time", 0); // for some pesky sites customProfile.SetPreference("browser.download.folderList", 2); customProfile.SetPreference("browser.download.dir", Directory.GetCurrentDirectory()); customProfile.SetPreference("browser.helperApps.neverAsk.saveToDisk", "text/vnd.wap.wml,application/x-trash,httpd/unix-directory,application/vnd.framemaker,application/octet-stream"); // ex: dgj.com, yld.com, zpo.com?, cwb.net, kuke.com customProfile.SetPreference("media.volume_scale", "0.0"); customProfile.SetPreference("dom.successive_dialog_time_limit", 1); FirefoxOptions fo = new FirefoxOptions(); fo.Profile = customProfile; fo.SetPreference("security.sandbox.content.level", 5); fo.AddAdditionalCapability("acceptInsecureCerts", true, true); fo.AcceptInsecureCertificates = true; fo.Profile.AcceptUntrustedCertificates = true; fo.Profile.AssumeUntrustedCertificateIssuer = true; fo.Profile.DeleteAfterUse = true; fo.UnhandledPromptBehavior = UnhandledPromptBehavior.Dismiss; fo.LogLevel = FirefoxDriverLogLevel.Trace; fo.SetPreference("webdriver.log.file", "d:\\selenium\\driver.log"); _driver = new FirefoxDriver(FirefoxDriverService.CreateDefaultService(), fo, TimeSpan.FromSeconds(60)); _driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(40); } public Screenshot GenerateScreenshot(string url, int width, int height) { int retries = 0; bool executed = false; while (retries < 4 && !executed) { try { // Selenium relies on a try catch model heavily and there are a lot of situation were it can't // get a correct response and consistent state when interacting with the driver and browser, namely when there is a command timeout. // so we need to proceed with the processing to give more time for the browser page to update itself // "clear" previous browsing _driver.Navigate().GoToUrl("about:blank"); _driver.Manage().Window.Size = new Size(width, height); string mainWindowHandle = _driver.CurrentWindowHandle; // load page try { _driver.Navigate().GoToUrl(url); } catch (WebDriverTimeoutException ex) { UpdateLog(String.Format("[{0}]WARNING: Timeout when getting " + url + " -> " + ex.Message, DateTime.Now)); } // take some extra time to try to load things up if (retries > 0) Thread.Sleep(retries * 20 * 1000); // stop alerts AND basic auth login, after timeout on page load. stop multiple alert windows IAlert alert; while ((alert = SeleniumExtras.WaitHelpers.ExpectedConditions.AlertIsPresent().Invoke(_driver)) != null) { //if (alert != null) alert.Dismiss(); Thread.Sleep(500); } // stop pop-ups foreach (string activeHandle in _driver.WindowHandles) { if (!activeHandle.Equals(mainWindowHandle)) { _driver.SwitchTo().Window(activeHandle).Close(); // set focus back on main window _driver.SwitchTo().Window(mainWindowHandle).SwitchTo(); } } // there is no bullet proof solution to get a complete page load so we will give always more X seconds for the page to load (ajax, flash, java, ...) var javaScriptExecutor = _driver as IJavaScriptExecutor; var waitPage = new WebDriverWait(_driver, TimeSpan.FromSeconds(10)); try { bool readyCondition(IWebDriver webDriver) => (bool)javaScriptExecutor.ExecuteScript("return (document.readyState == 'complete' && ((window.jQuery === undefined) || jQuery.active == 0))"); waitPage.Until(readyCondition); } catch (WebDriverTimeoutException ex) { UpdateLog(String.Format("[{0}]WARNING: Timeout when loading " + url + " -> " + ex.Message, DateTime.Now)); } //width = Convert.ToInt32(((IJavaScriptExecutor)_driver).ExecuteScript("return document.body.scrollWidth")); //height = Convert.ToInt32(((IJavaScriptExecutor)_driver).ExecuteScript("return document.body.scrollHeight")); //_driver.Manage().Window.Size = new Size(width, height); // wait for meta http-equiv redirects System.Collections.ObjectModel.ReadOnlyCollection<IWebElement> metas = _driver.FindElements(By.XPath("//meta[@http-equiv='refresh']")); if (metas.Count > 0) { IWebElement meta = metas[0]; string content = meta.GetAttribute("content"); content = content.Replace(" ", ""); int pos = content.IndexOf(';'); if (pos != -1 && pos != content.Length-1) { string timeout = content.Substring(0, pos); string newURL = content.Substring(pos + 1).Substring(4); // prevent an website asking for a refresh too long in the future int iTimeout = Convert.ToInt32(timeout); if (iTimeout <= 60) { //Thread.Sleep(Convert.ToInt32(timeout) * 1000); WebDriverWait waitRedirect = new WebDriverWait(_driver, TimeSpan.FromSeconds(iTimeout + 1)); try { waitRedirect.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.UrlContains(newURL)); } catch (WebDriverTimeoutException ex) { UpdateLog(String.Format("[{0}]WARNING: Timeout when waiting redirect from " + url + " to " + newURL + " -> " + ex.Message, DateTime.Now)); } } } } // always give an extra X seconds to load resources, like flash objects Thread.Sleep(5000); // if previous URL is the same as current URL then it might be the case where the browser hasn't // yet updated the page => Wait another X seconds to avoid getting staled information when getting the snapshot if (_driver.Url == "about:blank") Thread.Sleep(10000); executed = true; } catch (ThreadAbortException) { throw; } catch (Exception ex) { UpdateLog(String.Format("[{0}]WARNING: Exception when getting " + url + " -> " + ex.Message, DateTime.Now)); if (retries < 3) { QuitWebDriver(); CreateWebDriver(); UpdateLog(String.Format("[{0}]Going to retry...", DateTime.Now)); } retries++; } } if (!executed) { UpdateLog(String.Format("[{0}]ERROR: Giving up on " + url + " after {1} retries", DateTime.Now, retries - 1)); return null; } else return ((ITakesScreenshot)_driver).GetScreenshot(); } Actual results: Stacktrace on return ((ITakesScreenshot)_driver).GetScreenshot() [24-06-2018 22:00:20]Starting... [24-06-2018 22:00:58]Error on Snapshot 1 -> [Exception... "Failure" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: chrome://marionette/content/capture.js :: capture.canvas :: line 134" data: no] Trace-level log 1529874013125 geckodriver INFO geckodriver 0.21.0 1529874013165 geckodriver INFO Listening on 127.0.0.1:52889 1529874014270 mozrunner::runner INFO Running command: "D:\program files (x86)\Mozilla Firefox\firefox.exe" "-marionette" "-foreground" " -no-remote" "-profile" "D:\Users\Antonio\AppData\Local\Temp\rust_mozprofile.YSfJb1OrKtha" 1529874014278 geckodriver::marionette DEBUG Waiting 60s to connect to browser on 127.0.0.1:52893 1529874019757 geckodriver::marionette DEBUG Connected to Marionette on 127.0.0.1:52893 1529874020148 webdriver::server DEBUG <- 200 OK {"value": {"sessionId":"055b3f28-2ede-42b8-81f0-15a0137e906e","capabilities":{"acceptInsecur eCerts":true,"browserName":"firefox","browserVersion":"59.0.2","moz:accessibilityChecks":false,"moz:headless":false,"moz:processID":11608,"moz:profile ":"D:\Users\Antonio\AppData\Local\Temp\rust_mozprofile.YSfJb1OrKtha","moz:useNonSpecCompliantPointerOrigin":false,"moz:webdriverClick":true,"pag eLoadStrategy":"normal","platformName":"windows_nt","platformVersion":"6.1","rotatable":false,"timeouts":{"implicit":0,"pageLoad":300000,"script":3000 0}}}} 1529874020173 webdriver::server DEBUG -> POST /session/055b3f28-2ede-42b8-81f0-15a0137e906e/timeouts {"pageLoad":40000} 1529874020178 webdriver::server DEBUG <- 200 OK {"value": null} 1529874020471 webdriver::server DEBUG -> POST /session/055b3f28-2ede-42b8-81f0-15a0137e906e/url {"url":"about:blank"} 1529874020517 webdriver::server DEBUG <- 200 OK {"value": null} 1529874027353 webdriver::server DEBUG -> POST /session/055b3f28-2ede-42b8-81f0-15a0137e906e/window/rect {"width":1024,"height":768} 1529874027375 webdriver::server DEBUG <- 200 OK {"value": {"height":768,"width":1024,"x":4,"y":4}} 1529874028088 webdriver::server DEBUG -> GET /session/055b3f28-2ede-42b8-81f0-15a0137e906e/window 1529874028092 webdriver::server DEBUG <- 200 OK {"value":"4294967297"} 1529874029069 webdriver::server DEBUG -> POST /session/055b3f28-2ede-42b8-81f0-15a0137e906e/url {"url":"http://www.lwb.com"} 1529874030260 webdriver::server DEBUG <- 200 OK {"value": null} 1529874032193 webdriver::server DEBUG -> GET /session/055b3f28-2ede-42b8-81f0-15a0137e906e/alert/text 1529874032197 webdriver::server DEBUG <- 404 Not Found {"value":{"error":"no such alert","message":"No modal dialog is currently open","stac ktrace":"WebDriverError@chrome://marionette/content/error.js:172:5\nNoAlertOpenError@chrome://marionette/content/error.js:393:5\nGeckoDriver.prototype ._checkIfAlertIsPresent@chrome://marionette/content/driver.js:3167:11\nGeckoDriver.prototype.getTextFromDialog@chrome://marionette/content/driver.js:3 130:3\ndespatch@chrome://marionette/content/server.js:557:20\nexecute@chrome://marionette/content/server.js:531:11\nonPacket/<@chrome://marionette/con tent/server.js:506:15\nonPacket@chrome://marionette/content/server.js:505:8\n_onJSONObjectReady/<@chrome://marionette/content/transport.js:500:9\n"}} 1529874034100 webdriver::server DEBUG -> GET /session/055b3f28-2ede-42b8-81f0-15a0137e906e/window/handles 1529874034105 webdriver::server DEBUG <- 200 OK {"value":["4294967297"]} 1529874038068 webdriver::server DEBUG -> POST /session/055b3f28-2ede-42b8-81f0-15a0137e906e/execute/sync {"script":"return (document.readySt ate == 'complete' && ((window.jQuery === undefined) || jQuery.active == 0))","args":[]} 1529874038081 webdriver::server DEBUG <- 200 OK {"value":true} 1529874038925 webdriver::server DEBUG -> POST /session/055b3f28-2ede-42b8-81f0-15a0137e906e/elements {"using":"xpath","value":"//meta[@http- equiv='refresh']"} 1529874038939 webdriver::server DEBUG <- 200 OK {"value":[]} 1529874046146 webdriver::server DEBUG -> GET /session/055b3f28-2ede-42b8-81f0-15a0137e906e/url 1529874046155 webdriver::server DEBUG <- 200 OK {"value":"http://www.lwb.com/"} 1529874054179 webdriver::server DEBUG -> GET /session/055b3f28-2ede-42b8-81f0-15a0137e906e/screenshot 1529874054204 webdriver::server DEBUG <- 500 Internal Server Error {"value":{"error":"unknown error","message":"[Exception... "Failure" n sresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: chrome://marionette/content/capture.js :: capture.canvas :: line 134" data: no]" ,"stacktrace":"capture.canvas@chrome://marionette/content/capture.js:134:3\ncapture.viewport@chrome://marionette/content/capture.js:71:10\ntakeScreens hot@chrome://marionette/content/listener.js:1551:14\ndispatch/</req<@chrome://marionette/content/listener.js:491:14\ndispatch/<@chrome://marionette/co ntent/listener.js:486:15\n"}} Expected results: should have returned a snapshot of the web page's viewport, at 1024x768, in a string encoded in base64.
Triaging this issue to testing::geeckodriver as it seems to me the correct component.
Component: Untriaged → geckodriver
Product: Firefox → Testing
So this was originally filed as https://github.com/mozilla/geckodriver/issues/1306. I had a quick look and wanted to also create a bug. Good to see this has already been done. The failure actually happens when calling `scale()` on the canvas. I haven't had the time to further check that yet.
Status: UNCONFIRMED → NEW
Component: geckodriver → Marionette
Ever confirmed: true
Priority: -- → P3
Summary: WebDriver:TakeScreenshot generates error when web page has a big height → WebDriver:TakeScreenshot fails in canvas "scale()" for huge web pages
Hi, Following the originally launched ticket (https://github.com/mozilla/geckodriver/issues/1306), this errors seems reproducible in FF 63.0.1. Is this the same bug? 1. Browse to http://www.lwb.com/ in FF 2. Click the Ellipses in the URL Nav bar 3. Click 'Take a Screenshot' 4. Click 'Save full page' (top right) Firefox screencap will crash with this: Unhandled error: Exception ​ columnNumber: 0 ​ data: null ​ filename: "moz-extension://20e29653-f702-444e-982c-8d5f0a559b75/selector/shooter.js" ​ line: const imageData = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height); lineNumber: 87 ​ message: "" ​ name: "NS_ERROR_FAILURE" ​ result: 2147500037 ​ stack: "screenshotPageAsync@moz-extension://20e29653-f702-444e-982c-8d5f0a559b75/selector/shooter.js:87:47\nexports.downloadShot@moz-extension://20e29653-f702-444e-982c-8d5f0a559b75/selector/shooter.js:182:76\ndownloadShot@moz-extension://20e29653-f702-444e-982c-8d5f0a559b75/selector/uicontrol.js:103:5\nonDownloadPreview@moz-extension://20e29653-f702-444e-982c-8d5f0a559b75/selector/uicontrol.js:218:7\nassertIsTrusted/<@moz-extension://20e29653-f702-444e-982c-8d5f0a559b75/assertIsTrusted.js:17:12\nwatchFunction/this.catcher@moz-extension://20e29653-f702-444e-982c-8d5f0a559b75/catcher.js:55:16\n" ​
Flags: needinfo?(dburns)
abk, thanks for the report but this is not for Marionette but an extension, or the internal screenshot feature of Firefox. If it is still happening for you with a recent Nightly or Beta build of Firefox, please file a new bug under https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=Screenshots. The Marionette bug as filed here has basically already fixed by bug 1485730.
Status: NEW → RESOLVED
Closed: 7 years ago
Flags: needinfo?(dburns)
Resolution: --- → DUPLICATE
thank you for the clarification, I will do some more testing and lodge a new ticket :) It is indeed the internal screenshot feature of Firefox (which is implemented as an extension). Its amusing that two very similar bugs (canvas size issues) cropped up in two similar places :) A nice workaround is to load the target page in an iframe with limited height, then take a screenshot of the iframe, which seems to prevent the crash.
Product: Testing → Remote Protocol
You need to log in before you can comment on or make changes to this bug.