qa_tests.py (35139B)
1 #!/usr/bin/env python3 2 # This Source Code Form is subject to the terms of the Mozilla Public 3 # License, v. 2.0. If a copy of the MPL was not distributed with this 4 # file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 6 7 import os 8 import random 9 import subprocess 10 import tempfile 11 import time 12 13 from basic_tests import SnapTestsBase 14 from selenium.common.exceptions import TimeoutException 15 from selenium.webdriver.common.action_chains import ActionChains 16 from selenium.webdriver.common.by import By 17 from selenium.webdriver.common.keys import Keys 18 from selenium.webdriver.support import expected_conditions as EC 19 from selenium.webdriver.support.select import Select 20 21 22 class QATests(SnapTestsBase): 23 def __init__(self): 24 self._dir = "qa_tests" 25 self._http_server = None 26 self._http_tmpdir = None 27 28 super().__init__( 29 exp=os.path.join( 30 self._dir, f"qa_expectations_{self._distro_release()}.json" 31 ) 32 ) 33 34 def _distro_release(self): 35 with open("/etc/lsb-release") as lsb_release: 36 f = list( 37 filter( 38 lambda x: x.startswith("DISTRIB_RELEASE"), 39 lsb_release.read().split(), 40 ) 41 ) 42 return f[0].split("=")[1].replace(".", "") 43 44 def _start_local_http_server(self, port=45678): 45 """Start a local HTTP server with a 1MB random file for download tests.""" 46 # Create temporary directory 47 tmpdir = tempfile.mkdtemp(prefix="snap-test-http-") 48 49 # Generate 1MB random file 50 random_file = os.path.join(tmpdir, "testfile.iso") 51 with open(random_file, "wb") as f: 52 f.write(os.urandom(1024 * 1024)) # 1MB of random bytes 53 54 # Create simple HTML page with download link 55 html_file = os.path.join(tmpdir, "index.html") 56 with open(html_file, "w") as f: 57 f.write( 58 """<!DOCTYPE html> 59 <html> 60 <head><title>Test Download Page</title></head> 61 <body> 62 <h1>Download Test</h1> 63 <a href="testfile.iso" id="download-link" download="testfile.iso">Download 1MB File</a> 64 </body> 65 </html>""" 66 ) 67 68 # Start http.server as background process 69 server_process = subprocess.Popen( 70 ["python3", "-m", "http.server", str(port)], 71 cwd=tmpdir, 72 stdout=subprocess.DEVNULL, 73 stderr=subprocess.DEVNULL, 74 ) 75 76 # Give server time to start 77 time.sleep(0.5) 78 79 self._logger.info(f"Started local HTTP server on port {port} in {tmpdir}") 80 return server_process, tmpdir 81 82 def _stop_local_http_server(self, server_process, tmpdir): 83 """Stop the local HTTP server and clean up.""" 84 if server_process: 85 server_process.terminate() 86 server_process.wait() 87 self._logger.info("Stopped local HTTP server") 88 89 # Clean up temporary directory 90 if tmpdir and os.path.exists(tmpdir): 91 import shutil 92 93 shutil.rmtree(tmpdir) 94 95 def _test_audio_playback( 96 self, url, iframe_selector=None, click_to_play=False, video_selector=None 97 ): 98 self._logger.info(f"open url {url}") 99 if url: 100 self.open_tab(url) 101 102 if iframe_selector: 103 self._logger.info("find iframe") 104 iframe = self._wait.until( 105 EC.visibility_of_element_located((By.CSS_SELECTOR, iframe_selector)) 106 ) 107 self._driver.switch_to.frame(iframe) 108 109 self._logger.info("find video") 110 video = self._wait.until( 111 EC.visibility_of_element_located(( 112 By.CSS_SELECTOR, 113 video_selector or "video", 114 )) 115 ) 116 self._wait.until(lambda d: type(video.get_property("duration")) is float) 117 assert video.get_property("duration") > 0.0, "<video> duration null" 118 119 # For HE-AAC page, Google Drive does not like SPACE 120 if not click_to_play and video.get_property("autoplay") is False: 121 self._logger.info("force play") 122 video.send_keys(Keys.SPACE) 123 124 # Mostly for Google Drive video, click()/play() seems not to really 125 # work to trigger, but 'k' is required 126 if not click_to_play: 127 self._driver.execute_script("arguments[0].click();", video) 128 else: 129 video.send_keys("k") 130 131 ref_volume = video.get_property("volume") 132 133 self._logger.info("find video: wait readyState") 134 self._wait.until(lambda d: video.get_property("readyState") >= 4) 135 136 # Some videos sometimes self-pause? 137 self._logger.info( 138 "find video: check paused: {}".format(video.get_property("paused")) 139 ) 140 self._logger.info( 141 "find video: check autoplay: {}".format(video.get_property("autoplay")) 142 ) 143 if click_to_play or ( 144 not click_to_play and video.get_property("paused") is True 145 ): 146 self._driver.execute_script("arguments[0].play()", video) 147 148 self._logger.info("find video: sleep") 149 # let it play at least 500ms 150 time.sleep(0.5) 151 152 self._logger.info("find video: wait currentTime") 153 self._wait.until(lambda d: video.get_property("currentTime") >= 0.01) 154 assert video.get_property("currentTime") >= 0.01, ( 155 "<video> currentTime not moved" 156 ) 157 158 # this should pause 159 self._logger.info("find video: pause") 160 self._driver.execute_script("arguments[0].pause()", video) 161 datum = video.get_property("currentTime") 162 paused = video.get_property("paused") 163 time.sleep(1) 164 datum_after_sleep = video.get_property("currentTime") 165 self._logger.info(f"datum={datum} datum_after_sleep={datum_after_sleep}") 166 assert datum == datum_after_sleep, "<video> is sleeping" 167 assert paused is True, "<video> is paused" 168 169 self._logger.info("find video: unpause") 170 # unpause and verify playback 171 self._driver.execute_script("arguments[0].play()", video) 172 paused = video.get_property("paused") 173 assert paused is False, "<video> is not paused" 174 time.sleep(2) 175 datum_after_resume = video.get_property("currentTime") 176 self._logger.info( 177 f"datum_after_resume={datum_after_resume} datum_after_sleep={datum_after_sleep}" 178 ) 179 # we wait for 2s but it's not super accurate on CI (vbox VMs?), 180 # observed values +/- 15% so check for more that should avoid 181 # intermittent failures 182 assert datum_after_resume >= datum_after_sleep + 0.5, ( 183 "<video> progressed after pause" 184 ) 185 186 self._logger.info("find video: volume") 187 self._driver.execute_script( 188 "arguments[0].volume = arguments[1]", video, ref_volume * 0.25 189 ) 190 new_volume = video.get_property("volume") 191 assert new_volume == ref_volume * 0.25, ( 192 f"<video> sound volume increased from {ref_volume} to {ref_volume * 0.25} but got {new_volume}" 193 ) 194 195 self._logger.info("find video: done") 196 197 def _test_audio_video_playback(self, url): 198 iframe_css_selector = "iframe[id*=ucc-]" 199 self._logger.info(f"open url {url}") 200 self.open_tab(url) 201 self._logger.info("find thumbnail") 202 thumbnail = self._longwait.until( 203 EC.visibility_of_element_located((By.CSS_SELECTOR, "img")) 204 ) 205 self._logger.info("click") 206 self._driver.execute_script("arguments[0].click()", thumbnail) 207 self._logger.info("audio test") 208 self._test_audio_playback( 209 url=None, 210 iframe_selector=iframe_css_selector, 211 click_to_play=True, 212 ) 213 214 # switch back 215 self._driver.switch_to.parent_frame() 216 self._logger.info("try fullscreen") 217 218 fullscreen_button = self._wait.until( 219 EC.presence_of_element_located(( 220 By.CSS_SELECTOR, 221 "button[aria-label*='(f)']", 222 )) 223 ) 224 self._driver.execute_script("return arguments[0].click();", fullscreen_button) 225 time.sleep(1) 226 fullscreen = self._driver.execute_script("return document.fullscreen") 227 assert fullscreen, "<video> is fullscreen" 228 229 iframe = self._wait.until( 230 EC.visibility_of_element_located((By.CSS_SELECTOR, iframe_css_selector)) 231 ) 232 self._driver.switch_to.frame(iframe) 233 time.sleep(0.5) 234 # we are again in the iframe 235 self._logger.info("find video again") 236 video = self._wait.until( 237 EC.visibility_of_element_located((By.CSS_SELECTOR, "video")) 238 ) 239 240 self._driver.execute_script("arguments[0].pause();", video) 241 self._driver.execute_script("document.exitFullscreen()") 242 243 def test_h264_mov(self, exp): 244 """ 245 C95233 246 """ 247 248 self._test_audio_video_playback( 249 "https://drive.google.com/file/d/1lY6xYRR_KC0MGosopJA6Kn6uCvJ1RgQm/view?hl=en-US" 250 ) 251 252 return True 253 254 def test_he_aac(self, exp): 255 """ 256 C95239 257 """ 258 self._test_audio_playback( 259 url="https://www2.iis.fraunhofer.de/AAC/multichannel.html", 260 video_selector="p.inlineVideo > video", 261 ) 262 263 return True 264 265 def test_flac(self, exp): 266 """ 267 C95240 268 """ 269 270 self._test_audio_playback( 271 "https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.flac" 272 ) 273 274 return True 275 276 def test_mp3(self, exp): 277 """ 278 C95241 279 """ 280 self._test_audio_playback( 281 "https://freetestdata.com/wp-content/uploads/2021/09/Free_Test_Data_5MB_MP3.mp3" 282 ) 283 284 return True 285 286 def test_ogg(self, exp): 287 """ 288 C95244 289 """ 290 self._test_audio_playback( 291 "http://www.metadecks.org/software/sweep/audio/demos/beats1.ogg" 292 ) 293 294 return True 295 296 def test_custom_fonts(self, exp): 297 """ 298 C128146 299 """ 300 301 self.open_tab( 302 "http://codinginparadise.org/projects/svgweb/samples/demo.html?name=droid%20font1" 303 ) 304 305 renderer = self._wait.until( 306 EC.visibility_of_element_located((By.ID, "selectRenderer")) 307 ) 308 self._wait.until(lambda d: len(renderer.text) > 0) 309 310 renderer_drop = Select(renderer) 311 renderer_drop.select_by_visible_text("browser native svg") 312 313 font = self._wait.until(EC.visibility_of_element_located((By.ID, "selectSVG"))) 314 self._wait.until(lambda d: len(font.text) > 0) 315 316 font_drop = Select(font) 317 font_drop.select_by_value("droid font1") 318 319 svg_div = self._wait.until( 320 EC.visibility_of_element_located((By.ID, "__svg__random___1__object")) 321 ) 322 self._wait.until(lambda d: svg_div.is_displayed() is True) 323 324 self.assert_rendering(exp, svg_div) 325 326 return True 327 328 def pdf_select_zoom(self, value): 329 pdf_zoom = self._wait.until( 330 EC.visibility_of_element_located((By.ID, "scaleSelect")) 331 ) 332 self._wait.until(lambda d: len(pdf_zoom.text) > 0) 333 334 pdf_zoom_drop = Select(pdf_zoom) 335 pdf_zoom_drop.select_by_value(value) 336 337 def pdf_wait_div(self): 338 pdf_div = self._wait.until(EC.visibility_of_element_located((By.ID, "viewer"))) 339 self._wait.until(lambda d: pdf_div.is_displayed() is True) 340 return pdf_div 341 342 def pdf_get_page(self, page, long=False): 343 waiter = self._longwait if long is True else self._wait 344 page = waiter.until( 345 EC.visibility_of_element_located(( 346 By.CSS_SELECTOR, 347 f"div.page[data-page-number='{page}'] canvas", 348 )) 349 ) 350 351 self._wait.until( 352 lambda d: d.execute_script( 353 'return window.getComputedStyle(document.querySelector(".loadingInput.start"), "::after").getPropertyValue("visibility");' 354 ) 355 != "visible" 356 ) 357 # PDF.js can take time to settle and we don't have a nice way to wait 358 # for an event on it 359 time.sleep(1) 360 361 # Rendering can be slower on debug build so give more time to settle 362 if self.is_debug_build(): 363 time.sleep(3) 364 365 return page 366 367 def pdf_go_to_page(self, page): 368 pagenum = self._wait.until( 369 EC.visibility_of_element_located((By.ID, "pageNumber")) 370 ) 371 pagenum.send_keys(Keys.BACKSPACE) 372 pagenum.send_keys(f"{page}") 373 374 def test_pdf_navigation(self, exp): 375 """ 376 C3927 377 """ 378 379 self.open_tab("http://www.pdf995.com/samples/pdf.pdf") 380 381 # Test basic rendering 382 self.pdf_wait_div() 383 self.pdf_select_zoom("1") 384 self.pdf_get_page(1) 385 self.assert_rendering(exp["base"], self._driver) 386 387 # Navigating to page X, we know the PDF has 5 pages. 388 rand_page = random.randint(1, 5) 389 self.pdf_go_to_page(rand_page) 390 # the click step ensures we change page 391 self.pdf_wait_div().click() 392 # getting page X will wait on is_displayed() so if page X is not visible 393 # this will timeout 394 self.pdf_get_page(rand_page) 395 396 # press down/up/right/left/PageDown/PageUp/End/Home 397 key_presses = [ 398 (Keys.DOWN, "down"), 399 (Keys.UP, "up"), 400 (Keys.RIGHT, "right"), 401 (Keys.LEFT, "left"), 402 (Keys.PAGE_DOWN, "pagedown"), 403 (Keys.PAGE_UP, "pageup"), 404 (Keys.END, "end"), 405 (Keys.HOME, "home"), 406 ] 407 408 for key, ref in key_presses: 409 # reset to page 2 410 self.pdf_go_to_page(2) 411 pdfjs = self._wait.until( 412 EC.visibility_of_element_located((By.CSS_SELECTOR, "html")) 413 ) 414 pdfjs.send_keys(key) 415 self.pdf_get_page(2) 416 # give some time for rendering to update 417 time.sleep(0.2) 418 self._logger.info(f"assert {ref}") 419 self.assert_rendering(exp[ref], self._driver) 420 421 # click Next/Previous page 422 self.pdf_go_to_page(1) 423 button_next = self._wait.until( 424 EC.visibility_of_element_located((By.ID, "next")) 425 ) 426 button_next.click() 427 button_next.click() 428 self._logger.info("assert next twice 1 => 3") 429 self.assert_rendering(exp["next"], self._driver) 430 431 button_previous = self._wait.until( 432 EC.visibility_of_element_located((By.ID, "previous")) 433 ) 434 button_previous.click() 435 self._logger.info("assert previous 3 => 2") 436 self.assert_rendering(exp["previous"], self._driver) 437 438 secondary_menu = self._wait.until( 439 EC.visibility_of_element_located((By.ID, "secondaryToolbarToggle")) 440 ) 441 442 # Use tools button 443 # - first/lage page 444 # - rotate left/right 445 # - doc properties 446 menu_buttons = [ 447 "firstPage", 448 "lastPage", 449 "pageRotateCw", 450 "pageRotateCcw", 451 "documentProperties", 452 ] 453 454 for menu_id in menu_buttons: 455 self._logger.info(f"reset to page for {menu_id}") 456 if menu_id != "firstPage": 457 self.pdf_go_to_page(1) 458 else: 459 self.pdf_go_to_page(2) 460 time.sleep(0.2) 461 462 self._logger.info(f"click menu for {menu_id}") 463 # open menu 464 secondary_menu.click() 465 466 self._logger.info(f"find button for {menu_id}") 467 button_to_test = self._wait.until( 468 EC.visibility_of_element_located((By.ID, menu_id)) 469 ) 470 471 self._logger.info(f"click button for {menu_id}") 472 button_to_test.click() 473 474 try: 475 self._wait.until( 476 EC.invisibility_of_element_located((By.ID, "secondaryToolbar")) 477 ) 478 except TimeoutException: 479 # Menu does not close itself on those?? 480 if menu_id in ("pageRotateCw", "pageRotateCcw"): 481 self._logger.info(f"force close menu for {menu_id}") 482 secondary_menu.click() 483 self._logger.info(f"wait menu disappear for {menu_id}") 484 self._wait.until( 485 EC.invisibility_of_element_located((By.ID, "secondaryToolbar")) 486 ) 487 488 self._logger.info(f"assert {menu_id}") 489 self.assert_rendering(exp[menu_id], self._driver) 490 491 if menu_id == "documentProperties": 492 close = self._wait.until( 493 EC.visibility_of_element_located((By.ID, "documentPropertiesClose")) 494 ) 495 close.click() 496 497 self.pdf_go_to_page(1) 498 499 # - select text 500 secondary_menu.click() 501 text_selection = self._wait.until( 502 EC.visibility_of_element_located((By.ID, "cursorSelectTool")) 503 ) 504 text_selection.click() 505 506 action = ActionChains(self._driver) 507 paragraph = self._wait.until( 508 EC.visibility_of_element_located(( 509 By.CSS_SELECTOR, 510 "span[role=presentation]", 511 )) 512 ) 513 action.drag_and_drop_by_offset(paragraph, 50, 10).perform() 514 time.sleep(0.75) 515 try: 516 ref_screen_source = "select_text_with_highlight" 517 self._wait.until( 518 EC.visibility_of_element_located(( 519 By.CSS_SELECTOR, 520 "button.highlightButton", 521 )) 522 ) 523 except TimeoutException: 524 ref_screen_source = "select_text_without_highlight" 525 self._logger.info( 526 "Wait for pdf highlight button: timed out, maybe it is not there" 527 ) 528 finally: 529 time.sleep(0.75) 530 self.assert_rendering(exp[ref_screen_source], self._driver) 531 532 # release select selection 533 action.move_by_offset(0, 150).perform() 534 action.click() 535 # make sure we go back to page 1 536 self.pdf_go_to_page(1) 537 538 # - hand tool 539 secondary_menu.click() 540 hand_tool = self._wait.until( 541 EC.visibility_of_element_located((By.ID, "cursorHandTool")) 542 ) 543 hand_tool.click() 544 action.drag_and_drop_by_offset(paragraph, 0, -200).perform() 545 self.assert_rendering(exp["hand_tool"], self._driver) 546 547 return True 548 549 def test_pdf_zoom(self, exp): 550 """ 551 C3929 552 """ 553 554 self.open_tab("http://www.pdf995.com/samples/pdf.pdf") 555 556 self.pdf_wait_div() 557 558 zoom_levels = [ 559 ("1", 1, "p1_100p"), 560 ("0.5", 1, "p1_50p"), 561 ("0.75", 1, "p1_75p"), 562 ("1.5", 1, "p1_150p"), 563 ("4", 1, "p1_400p"), 564 ("page-actual", 1, "p1_actual"), 565 ("page-fit", 1, "p1_fit"), 566 ("page-width", 1, "p1_width"), 567 ] 568 569 for zoom, page, ref in zoom_levels: 570 self.pdf_select_zoom(zoom) 571 self.pdf_get_page(page, long=True) 572 self._logger.info(f"assert {ref}") 573 self.assert_rendering(exp[ref], self._driver) 574 575 return True 576 577 def test_pdf_download(self, exp): 578 """ 579 C936503 580 """ 581 582 self.open_tab( 583 "https://file-examples.com/index.php/sample-documents-download/sample-pdf-download/" 584 ) 585 586 try: 587 consent = self._wait.until( 588 EC.visibility_of_element_located((By.CSS_SELECTOR, ".fc-cta-consent")) 589 ) 590 consent.click() 591 except TimeoutException: 592 self._logger.info("Wait for consent form: timed out, maybe it is not here") 593 594 for iframe in self._driver.find_elements(By.CSS_SELECTOR, "iframe"): 595 self._driver.execute_script("arguments[0].remove();", iframe) 596 597 download_button = self._wait.until( 598 EC.presence_of_element_located((By.CSS_SELECTOR, ".download-button")) 599 ) 600 self._driver.execute_script("arguments[0].scrollIntoView();", download_button) 601 self._driver.execute_script("this.window.scrollBy(0, -100);") 602 self._wait.until( 603 EC.visibility_of_element_located((By.CSS_SELECTOR, ".download-button")) 604 ) 605 # clicking seems to break on CI because we nuke ads 606 self._driver.get(download_button.get_property("href")) 607 608 self.pdf_get_page(1) 609 610 self.assert_rendering(exp, self._driver) 611 612 return True 613 614 def context_menu_copy(self, element, mime_type): 615 action = ActionChains(self._driver) 616 617 # Open context menu and copy 618 action.context_click(element).perform() 619 self._driver.set_context("chrome") 620 context_menu = self._wait.until( 621 EC.visibility_of_element_located((By.ID, "contentAreaContextMenu")) 622 ) 623 copy = self._wait.until( 624 EC.visibility_of_element_located(( 625 By.ID, 626 ( 627 "context-copyimage-contents" 628 if mime_type.startswith("image/") 629 else "context-copy" 630 ), 631 )) 632 ) 633 copy.click() 634 self.wait_for_element_in_clipboard(mime_type, False) 635 context_menu.send_keys(Keys.ESCAPE) 636 637 # go back to content context 638 self._driver.set_context("content") 639 640 def verify_clipboard(self, mime_type, should_be_present): 641 self._driver.set_context("chrome") 642 in_clipboard = self._driver.execute_script( 643 "return Services.clipboard.hasDataMatchingFlavors([arguments[0]], Ci.nsIClipboard.kGlobalClipboard);", 644 mime_type, 645 ) 646 self._driver.set_context("content") 647 assert in_clipboard == should_be_present, ( 648 f"type {mime_type} should/should ({should_be_present}) not be in clipboard" 649 ) 650 651 def wait_for_element_in_clipboard(self, mime_type, context_change=False): 652 if context_change: 653 self._driver.set_context("chrome") 654 self._wait.until( 655 lambda d: self._driver.execute_script( 656 "return Services.clipboard.hasDataMatchingFlavors([arguments[0]], Ci.nsIClipboard.kGlobalClipboard);", 657 mime_type, 658 ) 659 is True 660 ) 661 if context_change: 662 self._driver.set_context("content") 663 664 def test_copy_paste_image_text(self, exp): 665 """ 666 C464474 667 """ 668 669 mystor = self.open_tab("https://mystor.github.io/dragndrop/#") 670 images = self.open_tab("https://1stwebdesigner.com/image-file-types/") 671 672 image = self._wait.until( 673 EC.presence_of_element_located((By.CSS_SELECTOR, ".wp-image-42224")) 674 ) 675 self._driver.execute_script("arguments[0].scrollIntoView();", image) 676 self._wait.until( 677 EC.visibility_of_element_located((By.CSS_SELECTOR, ".wp-image-42224")) 678 ) 679 self.verify_clipboard("image/png", False) 680 self.context_menu_copy(image, "image/png") 681 self.verify_clipboard("image/png", True) 682 683 self._driver.switch_to.window(mystor) 684 link = self._wait.until( 685 EC.visibility_of_element_located(( 686 By.CSS_SELECTOR, 687 "#testlist > li:nth-child(11) > a:nth-child(1)", 688 )) 689 ) 690 link.click() 691 drop_area = self._wait.until( 692 EC.visibility_of_element_located((By.CSS_SELECTOR, "html")) 693 ) 694 drop_area.click() 695 drop_area.send_keys(Keys.CONTROL + "v") 696 self.verify_clipboard("image/png", True) 697 698 matching_text = self._wait.until( 699 EC.visibility_of_element_located((By.ID, "matching")) 700 ) 701 assert matching_text.text == "MATCHING", "copy/paste image should match" 702 703 self._driver.switch_to.window(images) 704 text = self._wait.until( 705 EC.presence_of_element_located(( 706 By.CSS_SELECTOR, 707 ".entry-content > p:nth-child(1)", 708 )) 709 ) 710 self._driver.execute_script("arguments[0].scrollIntoView();", text) 711 712 action = ActionChains(self._driver) 713 action.drag_and_drop_by_offset(text, 50, 10).perform() 714 715 self.context_menu_copy(text, "text/plain") 716 717 self._driver.switch_to.window(mystor) 718 link = self._wait.until( 719 EC.visibility_of_element_located(( 720 By.CSS_SELECTOR, 721 "#testlist > li:nth-child(12) > a:nth-child(1)", 722 )) 723 ) 724 link.click() 725 drop_area = self._wait.until( 726 EC.visibility_of_element_located((By.CSS_SELECTOR, "html")) 727 ) 728 drop_area.click() 729 drop_area.send_keys(Keys.CONTROL + "v") 730 731 matching_text = self._wait.until( 732 EC.visibility_of_element_located((By.ID, "matching")) 733 ) 734 assert matching_text.text == "MATCHING", "copy/paste html should match" 735 736 return True 737 738 def accept_download(self): 739 # check the Firefox UI 740 self._driver.set_context("chrome") 741 download_button = self._wait.until( 742 EC.visibility_of_element_located((By.ID, "downloads-button")) 743 ) 744 download_button.click() 745 time.sleep(1) 746 747 download_item = self._wait.until( 748 EC.visibility_of_element_located(( 749 By.CSS_SELECTOR, 750 ".download-state .downloadTarget", 751 )) 752 ) 753 download_item.click() 754 download_name = download_item.get_property("value") 755 756 download_allow = self._wait.until( 757 EC.presence_of_element_located(( 758 By.ID, 759 "downloadsPanel-blockedSubview-unblockButton", 760 )) 761 ) 762 download_allow.click() 763 764 # back to page 765 self._driver.set_context("content") 766 767 return download_name 768 769 def wait_for_download(self): 770 # check the Firefox UI 771 self._driver.set_context("chrome") 772 download_button = self._wait.until( 773 EC.visibility_of_element_located((By.ID, "downloads-button")) 774 ) 775 download_button.click() 776 time.sleep(1) 777 778 download_item = self._wait.until( 779 EC.visibility_of_element_located(( 780 By.CSS_SELECTOR, 781 ".download-state .downloadTarget", 782 )) 783 ) 784 download_name = download_item.get_property("value") 785 self._logger.info(f"Waiting for download: {download_name}") 786 787 download_progress = self._wait.until( 788 EC.presence_of_element_located(( 789 By.CSS_SELECTOR, 790 ".download-state .downloadProgress", 791 )) 792 ) 793 self._logger.info( 794 "Download progress {}%".format(download_progress.get_property("value")) 795 ) 796 797 try: 798 self._wait.until(lambda d: download_progress.get_property("value") == 100) 799 except TimeoutException as ex: 800 details_normal = self._wait.until( 801 EC.presence_of_element_located(( 802 By.CSS_SELECTOR, 803 ".download-state .downloadDetailsNormal", 804 )) 805 ) 806 self._logger.info( 807 "Download details normal {}".format( 808 details_normal.get_property("value") 809 ) 810 ) 811 details_hover = self._wait.until( 812 EC.presence_of_element_located(( 813 By.CSS_SELECTOR, 814 ".download-state .downloadDetailsHover", 815 )) 816 ) 817 self._logger.info( 818 "Download details hover {}".format(details_hover.get_property("value")) 819 ) 820 raise ex 821 822 # back to page 823 self._driver.set_context("content") 824 return download_name 825 826 def change_download_folder(self, previous=None, new=None): 827 self._logger.info(f"Download change folder: {previous} => {new}") 828 self._driver.set_context("chrome") 829 self._driver.execute_script( 830 "Services.prefs.setIntPref('browser.download.folderList', 2);" 831 ) 832 self._driver.execute_script( 833 "Services.prefs.setCharPref('browser.download.dir', arguments[0]);", new 834 ) 835 download_dir_pref = self._driver.execute_script( 836 "return Services.prefs.getCharPref('browser.download.dir', null);" 837 ) 838 self._driver.set_context("content") 839 self._logger.info(f"Download folder pref: {download_dir_pref}") 840 assert download_dir_pref == new, ( 841 "download directory from pref should match new directory" 842 ) 843 844 def enable_downloads_debug(self): 845 self._driver.set_context("chrome") 846 self._logger.info("Setting downloads loglevel to Debug") 847 self._driver.execute_script( 848 "return Services.prefs.setStringPref('toolkit.download.loglevel', 'Debug');" 849 ) 850 self._driver.set_context("content") 851 852 def open_local(self): 853 self.enable_downloads_debug() 854 # Start local HTTP server with test file 855 server_process, tmpdir = self._start_local_http_server(port=45678) 856 # Store server info for cleanup 857 self._http_server = server_process 858 self._http_tmpdir = tmpdir 859 # Open local test page 860 download_site = self.open_tab("http://localhost:45678/") 861 return download_site 862 863 def get_local_1M(self): 864 return self._wait.until( 865 EC.presence_of_element_located(( 866 By.ID, 867 "download-link", 868 )) 869 ) 870 871 def test_download_folder_change(self, exp): 872 """ 873 C1756713 874 """ 875 876 download_site = self.open_local() 877 extra_small = self.get_local_1M() 878 self._driver.execute_script("arguments[0].click();", extra_small) 879 880 download_name = self.accept_download() 881 self.wait_for_download() 882 883 self.open_tab("about:preferences") 884 download_folder = self._wait.until( 885 EC.presence_of_element_located((By.ID, "chooseFolder")) 886 ) 887 if not download_folder.get_property("value"): 888 # Fallback to "downloadFoler" for older Firefox versions 889 download_folder = self._wait.until( 890 EC.presence_of_element_located((By.ID, "downloadFolder")) 891 ) 892 previous_folder = ( 893 download_folder.get_property("value") 894 .replace("\u2066", "") 895 .replace("\u2069", "") 896 ) 897 self._logger.info(f"Download folder from about:preferences: {previous_folder}") 898 if not os.path.isabs(previous_folder): 899 previous_folder = os.path.join(os.environ.get("HOME", ""), previous_folder) 900 with tempfile.TemporaryDirectory( 901 dir=os.environ.get("HOME"), prefix="snap-test-download" 902 ) as tmpdir: 903 assert os.path.isdir(tmpdir), "tmpdir download should exists" 904 905 download_1 = os.path.abspath(os.path.join(previous_folder, download_name)) 906 self._logger.info(f"Download 1 assert: {download_1}") 907 assert os.path.isfile(download_1), "downloaded file #1 should exists" 908 909 self.change_download_folder(previous_folder, tmpdir) 910 911 self._driver.switch_to.window(download_site) 912 self._driver.execute_script("arguments[0].click();", extra_small) 913 self.accept_download() 914 download_name2 = self.wait_for_download() 915 download_2 = os.path.join(tmpdir, download_name2) 916 917 self._logger.info(f"Download 2 assert: {download_2}") 918 assert os.path.isfile(download_2), "downloaded file #2 should exists" 919 920 # Cleanup local HTTP server 921 self._stop_local_http_server(self._http_server, self._http_tmpdir) 922 self._http_server = None 923 self._http_tmpdir = None 924 925 return True 926 927 def test_download_folder_removal(self, exp): 928 """ 929 C1756715 930 """ 931 932 download_site = self.open_local() 933 extra_small = self.get_local_1M() 934 935 with tempfile.TemporaryDirectory( 936 dir=os.environ.get("HOME"), prefix="snap-test-download-rm" 937 ) as tmpdir: 938 self.change_download_folder(None, tmpdir) 939 940 self._driver.switch_to.window(download_site) 941 self._driver.execute_script("arguments[0].click();", extra_small) 942 943 self.accept_download() 944 download_name = self.wait_for_download() 945 download_file = os.path.join(tmpdir, download_name) 946 self._logger.info(f"Download assert: {download_file}") 947 assert os.path.isdir(tmpdir), "tmpdir download should exists" 948 assert os.path.isfile(download_file), "downloaded file should exists" 949 950 self._driver.set_context("chrome") 951 download_button = self._wait.until( 952 EC.visibility_of_element_located((By.ID, "downloads-button")) 953 ) 954 download_button.click() 955 time.sleep(1) 956 957 download_details = self._wait.until( 958 EC.visibility_of_element_located(( 959 By.CSS_SELECTOR, 960 ".download-state .downloadDetailsNormal", 961 )) 962 ) 963 assert download_details.get_property("value").startswith("Completed"), ( 964 "download should be marked as completed" 965 ) 966 967 # TemporaryDirectory out of focus so folder removed 968 969 # Close panel we will re-open it 970 self._driver.execute_script("this.window.DownloadsButton.hide();") 971 self._wait.until( 972 EC.invisibility_of_element_located(( 973 By.CSS_SELECTOR, 974 ".download-state .downloadDetailsNormal", 975 )) 976 ) 977 978 assert os.path.isdir(tmpdir) is False, "tmpdir should have been removed" 979 assert os.path.isfile(download_file) is False, ( 980 "downloaded file should have been removed" 981 ) 982 983 download_button.click() 984 time.sleep(1) 985 986 download_item = self._wait.until( 987 EC.visibility_of_element_located(( 988 By.CSS_SELECTOR, 989 ".download-state .downloadTarget", 990 )) 991 ) 992 download_name_2 = download_item.get_property("value") 993 assert download_name == download_name_2, "downloaded names should match" 994 995 download_details = self._wait.until( 996 EC.visibility_of_element_located(( 997 By.CSS_SELECTOR, 998 ".download-state .downloadDetailsNormal", 999 )) 1000 ) 1001 assert download_details.get_property("value").startswith( 1002 "File moved or missing" 1003 ), "download panel should report file moved/missing" 1004 1005 self._driver.execute_script("this.window.DownloadsButton.hide();") 1006 1007 self._driver.set_context("content") 1008 1009 # Cleanup local HTTP server 1010 self._stop_local_http_server(self._http_server, self._http_tmpdir) 1011 self._http_server = None 1012 self._http_tmpdir = None 1013 1014 return True 1015 1016 1017 if __name__ == "__main__": 1018 QATests()