diff --git a/testing/marionette/chrome/test_dialog.xul b/testing/marionette/chrome/test_dialog.xul index 6d04bf41def1..45999e76f8dc 100644 --- a/testing/marionette/chrome/test_dialog.xul +++ b/testing/marionette/chrome/test_dialog.xul @@ -1,19 +1,31 @@ - - - - + - - + + + + + + + + diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py b/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py index f27aa74841e2..3231c7ff89e2 100644 --- a/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py @@ -10,54 +10,104 @@ import urllib from unittest import skip -from marionette_driver.by import By - -from marionette_harness import MarionetteTestCase, WindowManagerMixin +from marionette_driver import By +from marionette_driver.errors import JavascriptException, NoSuchWindowException +from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin def inline(doc, mime="text/html;charset=utf-8"): return "data:{0},{1}".format(mime, urllib.quote(doc)) -ELEMENT = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAVklEQVRoge3PMQ0AMAzAsPJHVWYbjEWTj/zx7O75oXk9AAISD6QWSC2QWiC1QGqB1AKpBVILpBZILZBaILVAaoHUAqkFUgukFkgtkFogtUBqgdT6BnIBMKa1DtYxhPkAAAAASUVORK5CYII=" -HIGHLIGHTED_ELEMENT = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAVklEQVRoge3PQRHAQAgAMfyrwhm1sb3JIwIyN3MvmJu53f01kRqRGpEakRqRGpEakRqRGpEakRqRGpEakRqRGpEakRqRGpEakRqRGpEakRqRmvciL/gAQgW/OxTpMPwAAAAASUVORK5CYII=" - - -box = inline( - "
") -long = inline("

foo") -short = inline("") -svg = inline(""" - -""", mime="image/svg+xml") +box = inline("

") +input = inline("") +long = inline("

foo

") +short = inline("") +svg = inline(""" + + + """, mime="image/svg+xml") class ScreenCaptureTestCase(MarionetteTestCase): - def assert_png(self, string): - """Test that string is a Base64 encoded PNG file.""" - image = base64.decodestring(string) + + def setUp(self): + super(ScreenCaptureTestCase, self).setUp() + + self._device_pixel_ratio = None + + @property + def device_pixel_ratio(self): + if self._device_pixel_ratio is None: + self._device_pixel_ratio = self.marionette.execute_script(""" + return window.devicePixelRatio + """) + return self._device_pixel_ratio + + @property + def document_element(self): + return self.marionette.find_element(By.CSS_SELECTOR, ":root") + + @property + def page_y_offset(self): + return self.marionette.execute_script("return window.pageYOffset") + + @property + def viewport_dimensions(self): + return self.marionette.execute_script(""" + return [arguments[0].clientWidth, + arguments[0].clientHeight]; + """, script_args=[self.document_element]) + + def assert_png(self, screenshot): + """Test that screenshot is a Base64 encoded PNG file.""" + image = base64.decodestring(screenshot) self.assertEqual(imghdr.what("", image), "png") - def get_image_dimensions(self, string): - self.assert_png(string) - image = base64.decodestring(string) + def assert_formats(self, element=None): + if element is None: + element = self.document_element + + screenshot_default = self.marionette.screenshot(element=element) + screenshot_image = self.marionette.screenshot(element=element, format="base64") + binary1 = self.marionette.screenshot(element=element, format="binary") + binary2 = self.marionette.screenshot(element=element, format="binary") + hash1 = self.marionette.screenshot(element=element, format="hash") + hash2 = self.marionette.screenshot(element=element, format="hash") + + # Valid data should have been returned + self.assert_png(screenshot_image) + self.assertEqual(imghdr.what("", binary1), "png") + self.assertEqual(screenshot_image, base64.b64encode(binary1)) + self.assertEqual(hash1, hashlib.sha256(screenshot_image).hexdigest()) + + # Different formats produce different data + self.assertNotEqual(screenshot_image, binary1) + self.assertNotEqual(screenshot_image, hash1) + self.assertNotEqual(binary1, hash1) + + # A second capture should be identical + self.assertEqual(screenshot_image, screenshot_default) + self.assertEqual(binary1, binary2) + self.assertEqual(hash1, hash2) + + def get_element_dimensions(self, element): + rect = element.rect + return rect["width"], rect["height"] + + def get_image_dimensions(self, screenshot): + self.assert_png(screenshot) + image = base64.decodestring(screenshot) width, height = struct.unpack(">LL", image[16:24]) return int(width), int(height) + def scale(self, rect): + return (int(rect[0] * self.device_pixel_ratio), + int(rect[1] * self.device_pixel_ratio)) + class TestScreenCaptureChrome(WindowManagerMixin, ScreenCaptureTestCase): - @property - def primary_window_dimensions(self): - current_window = self.marionette.current_chrome_window_handle - self.marionette.switch_to_window(self.start_window) - with self.marionette.using_context("chrome"): - rv = tuple(self.marionette.execute_script(""" - let el = document.documentElement; - let rect = el.getBoundingClientRect(); - return [rect.width, rect.height]; - """)) - self.marionette.switch_to_window(current_window) - return rv def setUp(self): super(TestScreenCaptureChrome, self).setUp() @@ -67,90 +117,245 @@ class TestScreenCaptureChrome(WindowManagerMixin, ScreenCaptureTestCase): self.close_all_windows() super(TestScreenCaptureChrome, self).tearDown() - # A full chrome window screenshot is not the outer dimensions of - # the window, but instead the bounding box of the inside - # . - def test_window(self): - ss = self.marionette.screenshot() - self.assert_png(ss) - self.assertEqual(self.primary_window_dimensions, - self.get_image_dimensions(ss)) + @property + def window_dimensions(self): + return tuple(self.marionette.execute_script(""" + let el = document.documentElement; + let rect = el.getBoundingClientRect(); + return [rect.width, rect.height]; + """)) - def test_chrome_delegation(self): - with self.marionette.using_context("content"): - content = self.marionette.screenshot() - chrome = self.marionette.screenshot() - self.assertNotEqual(content, chrome) + def open_dialog(self, url=None, width=None, height=None): + if url is None: + url = "chrome://marionette/content/test_dialog.xul" + + def opener(): + features = "chrome" + if height is not None: + features += ",height={}".format(height) + if width is not None: + features += ",width={}".format(width) - # This tests that GeckoDriver#takeScreenshot uses - # currentContext.document.documentElement instead of looking for a - # element, which does not exist for all windows. - def test_secondary_windows(self): - def open_window_with_js(): self.marionette.execute_script(""" - window.open('chrome://marionette/content/test_dialog.xul', 'foo', - 'dialog,height=200,width=300'); - """) + window.open(arguments[0], "", arguments[1]); + """, script_args=[url, features]) - new_window = self.open_window(open_window_with_js) - self.marionette.switch_to_window(new_window) + return self.open_window(opener) - ss = self.marionette.screenshot() - size = self.get_image_dimensions(ss) - self.assert_png(ss) - self.assertNotEqual(self.primary_window_dimensions, size) + def test_capture_different_context(self): + """Check that screenshots in content and chrome are different.""" + with self.marionette.using_context("content"): + screenshot_content = self.marionette.screenshot() + screenshot_chrome = self.marionette.screenshot() + self.assertNotEqual(screenshot_content, screenshot_chrome) + + @skip_if_mobile + def test_capture_element(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + # Ensure we only capture the element + el = self.marionette.find_element(By.ID, "test-list") + screenshot_element = self.marionette.screenshot(element=el) + self.assertEqual(self.scale(self.get_element_dimensions(el)), + self.get_image_dimensions(screenshot_element)) + + # Ensure we do not capture the full window + screenshot_dialog = self.marionette.screenshot() + self.assertNotEqual(screenshot_dialog, screenshot_element) self.marionette.close_chrome_window() self.marionette.switch_to_window(self.start_window) + def test_capture_flags(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) -class Content(ScreenCaptureTestCase): - @property - def body_scroll_dimensions(self): - return tuple(self.marionette.execute_script( - "return [document.body.scrollWidth, document.body.scrollHeight]")) + textbox = self.marionette.find_element(By.ID, "text-box") + textbox.send_keys("") + screenshot_focus = self.marionette.screenshot() - @property - def viewport_dimensions(self): - return tuple(self.marionette.execute_script(""" - let docEl = document.documentElement; - return [docEl.clientWidth, docEl.clientHeight];""")) + self.marionette.execute_script("arguments[0].blur();", script_args=[textbox]) + screenshot_no_focus = self.marionette.screenshot() - @property - def document_element(self): - return self.marionette.execute_script("return document.documentElement") + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) - @property - def page_y_offset(self): - return self.marionette.execute_script("return window.pageYOffset") + self.assertNotEqual(screenshot_focus, screenshot_no_focus) + + def test_capture_full_area(self): + # A full capture is not the outer dimensions of the window, + # but instead the bounding box of the window's root node (documentElement). + screenshot_full = self.marionette.screenshot() + screenshot_root = self.marionette.screenshot(element=self.document_element) + + self.assert_png(screenshot_full) + self.assert_png(screenshot_root) + self.assertEqual(screenshot_root, screenshot_full) + self.assertEqual(self.scale(self.get_element_dimensions(self.document_element)), + self.get_image_dimensions(screenshot_full)) + + def test_capture_viewport(self): + # Load a HTML test page into the chrome window to get scrollbars + test_page = self.marionette.absolute_url("test.html") + dialog = self.open_dialog(url=test_page, width=50, height=50) + self.marionette.switch_to_window(dialog) + + # Size of screenshot has to match viewport size + screenshot = self.marionette.screenshot(full=False) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.viewport_dimensions), + self.get_image_dimensions(screenshot)) + self.assertNotEqual(self.scale(self.window_dimensions), + self.get_image_dimensions(screenshot)) + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + @skip("https://bugzilla.mozilla.org/show_bug.cgi?id=1213875") + def test_capture_scroll_element_into_view(self): + pass + + def test_capture_window_already_closed(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + self.marionette.close_chrome_window() + + self.assertRaises(NoSuchWindowException, self.marionette.screenshot) + self.marionette.switch_to_window(self.start_window) + + def test_formats(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + self.assert_formats() + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + def test_format_unknown(self): + with self.assertRaises(ValueError): + self.marionette.screenshot(format="cheese") + + @skip_if_mobile + def test_highlight_elements(self): + dialog = self.open_dialog() + self.marionette.switch_to_window(dialog) + + # Highlighting the element itself shouldn't make the image larger + element = self.marionette.find_element(By.ID, "test-list") + screenshot_element = self.marionette.screenshot(element=element) + screenshot_highlight = self.marionette.screenshot(element=element, + highlights=[element]) + self.assertEqual(self.scale(self.get_element_dimensions(element)), + self.get_image_dimensions(screenshot_element)) + self.assertNotEqual(screenshot_element, screenshot_highlight) + + # Highlighting a sub element + button = self.marionette.find_element(By.ID, "choose-button") + screenshot_highlight_button = self.marionette.screenshot(element=element, + highlights=[button]) + self.assertNotEqual(screenshot_element, screenshot_highlight_button) + self.assertNotEqual(screenshot_highlight, screenshot_highlight_button) + + self.marionette.close_chrome_window() + self.marionette.switch_to_window(self.start_window) + + def test_highlight_element_not_seen(self): + """Check that for not found elements an exception is raised.""" + with self.marionette.using_context('content'): + self.marionette.navigate(box) + content_element = self.marionette.find_element(By.ID, "green") + + self.assertRaisesRegexp(JavascriptException, "Element reference not seen before", + self.marionette.screenshot, highlights=[content_element]) + + chrome_document_element = self.document_element + with self.marionette.using_context('content'): + self.assertRaisesRegexp(JavascriptException, "Element reference not seen before", + self.marionette.screenshot, + highlights=[chrome_document_element]) + + +class TestScreenCaptureContent(WindowManagerMixin, ScreenCaptureTestCase): def setUp(self): - ScreenCaptureTestCase.setUp(self) + super(TestScreenCaptureContent, self).setUp() self.marionette.set_context("content") - def test_html_document_element(self): + def tearDown(self): + self.close_all_tabs() + super(TestScreenCaptureContent, self).tearDown() + + @property + def scroll_dimensions(self): + return tuple(self.marionette.execute_script(""" + return [document.body.scrollWidth, document.body.scrollHeight] + """)) + + @skip_if_mobile # Needs application independent method to open a new tab + def test_capture_tab_already_closed(self): + tab = self.open_tab() + self.marionette.switch_to_window(tab) + self.marionette.close() + + self.assertRaises(NoSuchWindowException, self.marionette.screenshot) + self.marionette.switch_to_window(self.start_tab) + + def test_capture_element(self): + self.marionette.navigate(box) + el = self.marionette.find_element(By.TAG_NAME, "div") + screenshot = self.marionette.screenshot(element=el) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.get_element_dimensions(el)), + self.get_image_dimensions(screenshot)) + + @skip("https://bugzilla.mozilla.org/show_bug.cgi?id=1213875") + def test_capture_element_scrolled_into_view(self): self.marionette.navigate(long) - string = self.marionette.screenshot() - self.assert_png(string) - self.assertEqual( - self.body_scroll_dimensions, self.get_image_dimensions(string)) + el = self.marionette.find_element(By.TAG_NAME, "p") + screenshot = self.marionette.screenshot(element=el) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.get_element_dimensions(el)), + self.get_image_dimensions(screenshot)) + self.assertGreater(self.page_y_offset, 0) - def test_svg_document_element(self): + def test_capture_flags(self): + self.marionette.navigate(input) + + textbox = self.marionette.find_element(By.ID, "text-input") + textbox.send_keys("") + screenshot_focus = self.marionette.screenshot() + + self.marionette.execute_script("arguments[0].blur();", script_args=[textbox]) + screenshot_no_focus = self.marionette.screenshot() + + self.assertNotEqual(screenshot_focus, screenshot_no_focus) + + def test_capture_html_document_element(self): + self.marionette.navigate(long) + screenshot = self.marionette.screenshot() + self.assert_png(screenshot) + self.assertEqual(self.scale(self.scroll_dimensions), + self.get_image_dimensions(screenshot)) + + def test_capture_svg_document_element(self): self.marionette.navigate(svg) - doc_el = self.document_element - string = self.marionette.screenshot() - self.assert_png(string) - self.assertEqual((doc_el.rect["width"], doc_el.rect["height"]), - self.get_image_dimensions(string)) + screenshot = self.marionette.screenshot() + self.assert_png(screenshot) + self.assertEqual(self.scale(self.get_element_dimensions(self.document_element)), + self.get_image_dimensions(screenshot)) - def test_viewport(self): + def test_capture_viewport(self): + url = self.marionette.absolute_url("clicks.html") self.marionette.navigate(short) - string = self.marionette.screenshot(full=False) - self.assert_png(string) - self.assertEqual( - self.viewport_dimensions, self.get_image_dimensions(string)) + self.marionette.navigate(url) + screenshot = self.marionette.screenshot(full=False) + self.assert_png(screenshot) + self.assertEqual(self.scale(self.viewport_dimensions), + self.get_image_dimensions(screenshot)) - def test_viewport_after_scroll(self): + def test_capture_viewport_after_scroll(self): self.marionette.navigate(long) before = self.marionette.screenshot() el = self.marionette.find_element(By.TAG_NAME, "p") @@ -160,48 +365,32 @@ class Content(ScreenCaptureTestCase): self.assertNotEqual(before, after) self.assertGreater(self.page_y_offset, 0) - def test_element(self): + def test_formats(self): self.marionette.navigate(box) - el = self.marionette.find_element(By.TAG_NAME, "div") - string = self.marionette.screenshot(element=el) - self.assert_png(string) - self.assertEqual( - (el.rect["width"], el.rect["height"]), self.get_image_dimensions(string)) - self.assertEqual(ELEMENT, string) - @skip("https://bugzilla.mozilla.org/show_bug.cgi?id=1213875") - def test_element_scrolled_into_view(self): - self.marionette.navigate(long) - el = self.marionette.find_element(By.TAG_NAME, "p") - string = self.marionette.screenshot(element=el) - self.assert_png(string) - self.assertEqual( - (el.rect["width"], el.rect["height"]), self.get_image_dimensions(string)) - self.assertGreater(self.page_y_offset, 0) + # Use a smaller region to speed up the test + element = self.marionette.find_element(By.TAG_NAME, "div") + self.assert_formats(element=element) - def test_element_with_highlight(self): - self.marionette.navigate(box) - el = self.marionette.find_element(By.TAG_NAME, "div") - string = self.marionette.screenshot(element=el, highlights=[el]) - self.assert_png(string) - self.assertEqual( - (el.rect["width"], el.rect["height"]), self.get_image_dimensions(string)) - self.assertEqual(HIGHLIGHTED_ELEMENT, string) - - def test_binary_element(self): - self.marionette.navigate(box) - el = self.marionette.find_element(By.TAG_NAME, "div") - bin = self.marionette.screenshot(element=el, format="binary") - enc = base64.b64encode(bin) - self.assertEqual(ELEMENT, enc) - - def test_unknown_format(self): + def test_format_unknown(self): with self.assertRaises(ValueError): self.marionette.screenshot(format="cheese") - def test_hash_format(self): + def test_highlight_elements(self): self.marionette.navigate(box) - el = self.marionette.find_element(By.TAG_NAME, "div") - content = self.marionette.screenshot(element=el, format="hash") - hash = hashlib.sha256(ELEMENT).hexdigest() - self.assertEqual(content, hash) + element = self.marionette.find_element(By.TAG_NAME, "div") + + # Highlighting the element itself shouldn't make the image larger + screenshot_element = self.marionette.screenshot(element=element) + screenshot_highlight = self.marionette.screenshot(element=element, + highlights=[element]) + self.assertEqual(self.scale(self.get_element_dimensions(element)), + self.get_image_dimensions(screenshot_highlight)) + self.assertNotEqual(screenshot_element, screenshot_highlight) + + # Highlighting a sub element + paragraph = self.marionette.find_element(By.ID, "green") + screenshot_highlight_paragraph = self.marionette.screenshot(element=element, + highlights=[paragraph]) + self.assertNotEqual(screenshot_element, screenshot_highlight_paragraph) + self.assertNotEqual(screenshot_highlight, screenshot_highlight_paragraph)