#!/usr/bin/env python3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.


import os
import random
import subprocess
import tempfile
import time

from basic_tests import SnapTestsBase
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select


class QATests(SnapTestsBase):
    def __init__(self):
        self._dir = "qa_tests"
        self._http_server = None
        self._http_tmpdir = None

        super().__init__(
            exp=os.path.join(
                self._dir, f"qa_expectations_{self._distro_release()}.json"
            )
        )

    def _distro_release(self):
        with open("/etc/lsb-release") as lsb_release:
            f = list(
                filter(
                    lambda x: x.startswith("DISTRIB_RELEASE"),
                    lsb_release.read().split(),
                )
            )
            return f[0].split("=")[1].replace(".", "")

    def _start_local_http_server(self, port=45678):
        """Start a local HTTP server with a 1MB random file for download tests."""
        # Create temporary directory
        tmpdir = tempfile.mkdtemp(prefix="snap-test-http-")

        # Generate 1MB random file
        random_file = os.path.join(tmpdir, "testfile.iso")
        with open(random_file, "wb") as f:
            f.write(os.urandom(1024 * 1024))  # 1MB of random bytes

        # Create simple HTML page with download link
        html_file = os.path.join(tmpdir, "index.html")
        with open(html_file, "w") as f:
            f.write(
                """<!DOCTYPE html>
<html>
<head><title>Test Download Page</title></head>
<body>
<h1>Download Test</h1>
<a href="testfile.iso" id="download-link" download="testfile.iso">Download 1MB File</a>
</body>
</html>"""
            )

        # Start http.server as background process
        server_process = subprocess.Popen(
            ["python3", "-m", "http.server", str(port)],
            cwd=tmpdir,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )

        # Give server time to start
        time.sleep(0.5)

        self._logger.info(f"Started local HTTP server on port {port} in {tmpdir}")
        return server_process, tmpdir

    def _stop_local_http_server(self, server_process, tmpdir):
        """Stop the local HTTP server and clean up."""
        if server_process:
            server_process.terminate()
            server_process.wait()
            self._logger.info("Stopped local HTTP server")

        # Clean up temporary directory
        if tmpdir and os.path.exists(tmpdir):
            import shutil

            shutil.rmtree(tmpdir)

    def _test_audio_playback(
        self, url, iframe_selector=None, click_to_play=False, video_selector=None
    ):
        self._logger.info(f"open url {url}")
        if url:
            self.open_tab(url)

        if iframe_selector:
            self._logger.info("find iframe")
            iframe = self._wait.until(
                EC.visibility_of_element_located((By.CSS_SELECTOR, iframe_selector))
            )
            self._driver.switch_to.frame(iframe)

        self._logger.info("find video")
        video = self._wait.until(
            EC.visibility_of_element_located((
                By.CSS_SELECTOR,
                video_selector or "video",
            ))
        )
        self._wait.until(lambda d: type(video.get_property("duration")) is float)
        assert video.get_property("duration") > 0.0, "<video> duration null"

        # For HE-AAC page, Google Drive does not like SPACE
        if not click_to_play and video.get_property("autoplay") is False:
            self._logger.info("force play")
            video.send_keys(Keys.SPACE)

        # Mostly for Google Drive video, click()/play() seems not to really
        # work to trigger, but 'k' is required
        if not click_to_play:
            self._driver.execute_script("arguments[0].click();", video)
        else:
            video.send_keys("k")

        ref_volume = video.get_property("volume")

        self._logger.info("find video: wait readyState")
        self._wait.until(lambda d: video.get_property("readyState") >= 4)

        # Some videos sometimes self-pause?
        self._logger.info(
            "find video: check paused: {}".format(video.get_property("paused"))
        )
        self._logger.info(
            "find video: check autoplay: {}".format(video.get_property("autoplay"))
        )
        if click_to_play or (
            not click_to_play and video.get_property("paused") is True
        ):
            self._driver.execute_script("arguments[0].play()", video)

        self._logger.info("find video: sleep")
        # let it play at least 500ms
        time.sleep(0.5)

        self._logger.info("find video: wait currentTime")
        self._wait.until(lambda d: video.get_property("currentTime") >= 0.01)
        assert video.get_property("currentTime") >= 0.01, (
            "<video> currentTime not moved"
        )

        # this should pause
        self._logger.info("find video: pause")
        self._driver.execute_script("arguments[0].pause()", video)
        datum = video.get_property("currentTime")
        paused = video.get_property("paused")
        time.sleep(1)
        datum_after_sleep = video.get_property("currentTime")
        self._logger.info(f"datum={datum} datum_after_sleep={datum_after_sleep}")
        assert datum == datum_after_sleep, "<video> is sleeping"
        assert paused is True, "<video> is paused"

        self._logger.info("find video: unpause")
        # unpause and verify playback
        self._driver.execute_script("arguments[0].play()", video)
        paused = video.get_property("paused")
        assert paused is False, "<video> is not paused"
        time.sleep(2)
        datum_after_resume = video.get_property("currentTime")
        self._logger.info(
            f"datum_after_resume={datum_after_resume} datum_after_sleep={datum_after_sleep}"
        )
        # we wait for 2s but it's not super accurate on CI (vbox VMs?),
        # observed values +/- 15% so check for more that should avoid
        # intermittent failures
        assert datum_after_resume >= datum_after_sleep + 0.5, (
            "<video> progressed after pause"
        )

        self._logger.info("find video: volume")
        self._driver.execute_script(
            "arguments[0].volume = arguments[1]", video, ref_volume * 0.25
        )
        new_volume = video.get_property("volume")
        assert new_volume == ref_volume * 0.25, (
            f"<video> sound volume increased from {ref_volume} to {ref_volume * 0.25} but got {new_volume}"
        )

        self._logger.info("find video: done")

    def _test_audio_video_playback(self, url):
        iframe_css_selector = "iframe[id*=ucc-]"
        self._logger.info(f"open url {url}")
        self.open_tab(url)
        self._logger.info("find thumbnail")
        thumbnail = self._longwait.until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "img"))
        )
        self._logger.info("click")
        self._driver.execute_script("arguments[0].click()", thumbnail)
        self._logger.info("audio test")
        self._test_audio_playback(
            url=None,
            iframe_selector=iframe_css_selector,
            click_to_play=True,
        )

        # switch back
        self._driver.switch_to.parent_frame()
        self._logger.info("try fullscreen")

        fullscreen_button = self._wait.until(
            EC.presence_of_element_located((
                By.CSS_SELECTOR,
                "button[aria-label*='(f)']",
            ))
        )
        self._driver.execute_script("return arguments[0].click();", fullscreen_button)
        time.sleep(1)
        fullscreen = self._driver.execute_script("return document.fullscreen")
        assert fullscreen, "<video> is fullscreen"

        iframe = self._wait.until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, iframe_css_selector))
        )
        self._driver.switch_to.frame(iframe)
        time.sleep(0.5)
        # we are again in the iframe
        self._logger.info("find video again")
        video = self._wait.until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "video"))
        )

        self._driver.execute_script("arguments[0].pause();", video)
        self._driver.execute_script("document.exitFullscreen()")

    def test_h264_mov(self, exp):
        """
        C95233
        """

        self._test_audio_video_playback(
            "https://drive.google.com/file/d/1lY6xYRR_KC0MGosopJA6Kn6uCvJ1RgQm/view?hl=en-US"
        )

        return True

    def test_he_aac(self, exp):
        """
        C95239
        """
        self._test_audio_playback(
            url="https://www2.iis.fraunhofer.de/AAC/multichannel.html",
            video_selector="p.inlineVideo > video",
        )

        return True

    def test_flac(self, exp):
        """
        C95240
        """

        self._test_audio_playback(
            "https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.flac"
        )

        return True

    def test_mp3(self, exp):
        """
        C95241
        """
        self._test_audio_playback(
            "https://freetestdata.com/wp-content/uploads/2021/09/Free_Test_Data_5MB_MP3.mp3"
        )

        return True

    def test_ogg(self, exp):
        """
        C95244
        """
        self._test_audio_playback(
            "http://www.metadecks.org/software/sweep/audio/demos/beats1.ogg"
        )

        return True

    def test_custom_fonts(self, exp):
        """
        C128146
        """

        self.open_tab(
            "http://codinginparadise.org/projects/svgweb/samples/demo.html?name=droid%20font1"
        )

        renderer = self._wait.until(
            EC.visibility_of_element_located((By.ID, "selectRenderer"))
        )
        self._wait.until(lambda d: len(renderer.text) > 0)

        renderer_drop = Select(renderer)
        renderer_drop.select_by_visible_text("browser native svg")

        font = self._wait.until(EC.visibility_of_element_located((By.ID, "selectSVG")))
        self._wait.until(lambda d: len(font.text) > 0)

        font_drop = Select(font)
        font_drop.select_by_value("droid font1")

        svg_div = self._wait.until(
            EC.visibility_of_element_located((By.ID, "__svg__random___1__object"))
        )
        self._wait.until(lambda d: svg_div.is_displayed() is True)

        self.assert_rendering(exp, svg_div)

        return True

    def pdf_select_zoom(self, value):
        pdf_zoom = self._wait.until(
            EC.visibility_of_element_located((By.ID, "scaleSelect"))
        )
        self._wait.until(lambda d: len(pdf_zoom.text) > 0)

        pdf_zoom_drop = Select(pdf_zoom)
        pdf_zoom_drop.select_by_value(value)

    def pdf_wait_div(self):
        pdf_div = self._wait.until(EC.visibility_of_element_located((By.ID, "viewer")))
        self._wait.until(lambda d: pdf_div.is_displayed() is True)
        return pdf_div

    def pdf_get_page(self, page, long=False):
        waiter = self._longwait if long is True else self._wait
        page = waiter.until(
            EC.visibility_of_element_located((
                By.CSS_SELECTOR,
                f"div.page[data-page-number='{page}'] canvas",
            ))
        )

        self._wait.until(
            lambda d: (
                d.execute_script(
                    'return window.getComputedStyle(document.querySelector(".loadingInput.start"), "::after").getPropertyValue("visibility");'
                )
                != "visible"
            )
        )
        # PDF.js can take time to settle and we don't have a nice way to wait
        # for an event on it
        time.sleep(1)

        # Rendering can be slower on debug build so give more time to settle
        if self.is_debug_build():
            time.sleep(3)

        return page

    def pdf_go_to_page(self, page):
        pagenum = self._wait.until(
            EC.visibility_of_element_located((By.ID, "pageNumber"))
        )
        pagenum.send_keys(Keys.BACKSPACE)
        pagenum.send_keys(f"{page}")

    def test_pdf_navigation(self, exp):
        """
        C3927
        """

        self.open_tab("http://www.pdf995.com/samples/pdf.pdf")

        # Test basic rendering
        self.pdf_wait_div()
        self.pdf_select_zoom("1")
        self.pdf_get_page(1)
        self.assert_rendering(exp["base"], self._driver)

        # Navigating to page X, we know the PDF has 5 pages.
        rand_page = random.randint(1, 5)
        self.pdf_go_to_page(rand_page)
        # the click step ensures we change page
        self.pdf_wait_div().click()
        # getting page X will wait on is_displayed() so if page X is not visible
        # this will timeout
        self.pdf_get_page(rand_page)

        # press down/up/right/left/PageDown/PageUp/End/Home
        key_presses = [
            (Keys.DOWN, "down"),
            (Keys.UP, "up"),
            (Keys.RIGHT, "right"),
            (Keys.LEFT, "left"),
            (Keys.PAGE_DOWN, "pagedown"),
            (Keys.PAGE_UP, "pageup"),
            (Keys.END, "end"),
            (Keys.HOME, "home"),
        ]

        for key, ref in key_presses:
            # reset to page 2
            self.pdf_go_to_page(2)
            pdfjs = self._wait.until(
                EC.visibility_of_element_located((By.CSS_SELECTOR, "html"))
            )
            pdfjs.send_keys(key)
            self.pdf_get_page(2)
            # give some time for rendering to update
            time.sleep(0.2)
            self._logger.info(f"assert {ref}")
            self.assert_rendering(exp[ref], self._driver)

        # click Next/Previous page
        self.pdf_go_to_page(1)
        button_next = self._wait.until(
            EC.visibility_of_element_located((By.ID, "next"))
        )
        button_next.click()
        button_next.click()
        self._logger.info("assert next twice 1 => 3")
        self.assert_rendering(exp["next"], self._driver)

        button_previous = self._wait.until(
            EC.visibility_of_element_located((By.ID, "previous"))
        )
        button_previous.click()
        self._logger.info("assert previous 3 => 2")
        self.assert_rendering(exp["previous"], self._driver)

        secondary_menu = self._wait.until(
            EC.visibility_of_element_located((By.ID, "secondaryToolbarToggle"))
        )

        # Use tools button
        #  - first/lage page
        #  - rotate left/right
        #  - doc properties
        menu_buttons = [
            "firstPage",
            "lastPage",
            "pageRotateCw",
            "pageRotateCcw",
            "documentProperties",
        ]

        for menu_id in menu_buttons:
            self._logger.info(f"reset to page for {menu_id}")
            if menu_id != "firstPage":
                self.pdf_go_to_page(1)
            else:
                self.pdf_go_to_page(2)
            time.sleep(0.2)

            self._logger.info(f"click menu for {menu_id}")
            # open menu
            secondary_menu.click()

            self._logger.info(f"find button for {menu_id}")
            button_to_test = self._wait.until(
                EC.visibility_of_element_located((By.ID, menu_id))
            )

            self._logger.info(f"click button for {menu_id}")
            button_to_test.click()

            try:
                self._wait.until(
                    EC.invisibility_of_element_located((By.ID, "secondaryToolbar"))
                )
            except TimeoutException:
                # Menu does not close itself on those??
                if menu_id in ("pageRotateCw", "pageRotateCcw"):
                    self._logger.info(f"force close menu for {menu_id}")
                    secondary_menu.click()
                    self._logger.info(f"wait menu disappear for {menu_id}")
                    self._wait.until(
                        EC.invisibility_of_element_located((By.ID, "secondaryToolbar"))
                    )

            self._logger.info(f"assert {menu_id}")
            self.assert_rendering(exp[menu_id], self._driver)

            if menu_id == "documentProperties":
                close = self._wait.until(
                    EC.visibility_of_element_located((By.ID, "documentPropertiesClose"))
                )
                close.click()

        self.pdf_go_to_page(1)

        #  - select text
        secondary_menu.click()
        text_selection = self._wait.until(
            EC.visibility_of_element_located((By.ID, "cursorSelectTool"))
        )
        text_selection.click()

        action = ActionChains(self._driver)
        paragraph = self._wait.until(
            EC.visibility_of_element_located((
                By.CSS_SELECTOR,
                "span[role=presentation]",
            ))
        )
        action.drag_and_drop_by_offset(paragraph, 50, 10).perform()
        time.sleep(0.75)
        try:
            ref_screen_source = "select_text_with_highlight"
            self._wait.until(
                EC.visibility_of_element_located((
                    By.CSS_SELECTOR,
                    "button.highlightButton",
                ))
            )
        except TimeoutException:
            ref_screen_source = "select_text_without_highlight"
            self._logger.info(
                "Wait for pdf highlight button: timed out, maybe it is not there"
            )
        finally:
            time.sleep(0.75)
            self.assert_rendering(exp[ref_screen_source], self._driver)

        # release select selection
        action.move_by_offset(0, 150).perform()
        action.click()
        # make sure we go back to page 1
        self.pdf_go_to_page(1)

        #  - hand tool
        secondary_menu.click()
        hand_tool = self._wait.until(
            EC.visibility_of_element_located((By.ID, "cursorHandTool"))
        )
        hand_tool.click()
        action.drag_and_drop_by_offset(paragraph, 0, -200).perform()
        self.assert_rendering(exp["hand_tool"], self._driver)

        return True

    def test_pdf_zoom(self, exp):
        """
        C3929
        """

        self.open_tab("http://www.pdf995.com/samples/pdf.pdf")

        self.pdf_wait_div()

        zoom_levels = [
            ("1", 1, "p1_100p"),
            ("0.5", 1, "p1_50p"),
            ("0.75", 1, "p1_75p"),
            ("1.5", 1, "p1_150p"),
            ("4", 1, "p1_400p"),
            ("page-actual", 1, "p1_actual"),
            ("page-fit", 1, "p1_fit"),
            ("page-width", 1, "p1_width"),
        ]

        for zoom, page, ref in zoom_levels:
            self.pdf_select_zoom(zoom)
            self.pdf_get_page(page, long=True)
            self._logger.info(f"assert {ref}")
            self.assert_rendering(exp[ref], self._driver)

        return True

    def test_pdf_download(self, exp):
        """
        C936503
        """

        self.open_tab(
            "https://file-examples.com/index.php/sample-documents-download/sample-pdf-download/"
        )

        try:
            consent = self._wait.until(
                EC.visibility_of_element_located((By.CSS_SELECTOR, ".fc-cta-consent"))
            )
            consent.click()
        except TimeoutException:
            self._logger.info("Wait for consent form: timed out, maybe it is not here")

        for iframe in self._driver.find_elements(By.CSS_SELECTOR, "iframe"):
            self._driver.execute_script("arguments[0].remove();", iframe)

        download_button = self._wait.until(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".download-button"))
        )
        self._driver.execute_script("arguments[0].scrollIntoView();", download_button)
        self._driver.execute_script("this.window.scrollBy(0, -100);")
        self._wait.until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, ".download-button"))
        )
        # clicking seems to break on CI because we nuke ads
        self._driver.get(download_button.get_property("href"))

        self.pdf_get_page(1)

        self.assert_rendering(exp, self._driver)

        return True

    def context_menu_copy(self, element, mime_type):
        action = ActionChains(self._driver)

        # Open context menu and copy
        action.context_click(element).perform()
        self._driver.set_context("chrome")
        context_menu = self._wait.until(
            EC.visibility_of_element_located((By.ID, "contentAreaContextMenu"))
        )
        copy = self._wait.until(
            EC.visibility_of_element_located((
                By.ID,
                (
                    "context-copyimage-contents"
                    if mime_type.startswith("image/")
                    else "context-copy"
                ),
            ))
        )
        copy.click()
        self.wait_for_element_in_clipboard(mime_type, False)
        context_menu.send_keys(Keys.ESCAPE)

        # go back to content context
        self._driver.set_context("content")

    def verify_clipboard(self, mime_type, should_be_present):
        self._driver.set_context("chrome")
        in_clipboard = self._driver.execute_script(
            "return Services.clipboard.hasDataMatchingFlavors([arguments[0]], Ci.nsIClipboard.kGlobalClipboard);",
            mime_type,
        )
        self._driver.set_context("content")
        assert in_clipboard == should_be_present, (
            f"type {mime_type} should/should ({should_be_present}) not be in clipboard"
        )

    def wait_for_element_in_clipboard(self, mime_type, context_change=False):
        if context_change:
            self._driver.set_context("chrome")
        self._wait.until(
            lambda d: (
                self._driver.execute_script(
                    "return Services.clipboard.hasDataMatchingFlavors([arguments[0]], Ci.nsIClipboard.kGlobalClipboard);",
                    mime_type,
                )
                is True
            )
        )
        if context_change:
            self._driver.set_context("content")

    def test_copy_paste_image_text(self, exp):
        """
        C464474
        """

        mystor = self.open_tab("https://mystor.github.io/dragndrop/#")
        images = self.open_tab("https://1stwebdesigner.com/image-file-types/")

        image = self._wait.until(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".wp-image-42224"))
        )
        self._driver.execute_script("arguments[0].scrollIntoView();", image)
        self._wait.until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, ".wp-image-42224"))
        )
        self.verify_clipboard("image/png", False)
        self.context_menu_copy(image, "image/png")
        self.verify_clipboard("image/png", True)

        self._driver.switch_to.window(mystor)
        link = self._wait.until(
            EC.visibility_of_element_located((
                By.CSS_SELECTOR,
                "#testlist > li:nth-child(11) > a:nth-child(1)",
            ))
        )
        link.click()
        drop_area = self._wait.until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "html"))
        )
        drop_area.click()
        drop_area.send_keys(Keys.CONTROL + "v")
        self.verify_clipboard("image/png", True)

        matching_text = self._wait.until(
            EC.visibility_of_element_located((By.ID, "matching"))
        )
        assert matching_text.text == "MATCHING", "copy/paste image should match"

        self._driver.switch_to.window(images)
        text = self._wait.until(
            EC.presence_of_element_located((
                By.CSS_SELECTOR,
                ".entry-content > p:nth-child(1)",
            ))
        )
        self._driver.execute_script("arguments[0].scrollIntoView();", text)

        action = ActionChains(self._driver)
        action.drag_and_drop_by_offset(text, 50, 10).perform()

        self.context_menu_copy(text, "text/plain")

        self._driver.switch_to.window(mystor)
        link = self._wait.until(
            EC.visibility_of_element_located((
                By.CSS_SELECTOR,
                "#testlist > li:nth-child(12) > a:nth-child(1)",
            ))
        )
        link.click()
        drop_area = self._wait.until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "html"))
        )
        drop_area.click()
        drop_area.send_keys(Keys.CONTROL + "v")

        matching_text = self._wait.until(
            EC.visibility_of_element_located((By.ID, "matching"))
        )
        assert matching_text.text == "MATCHING", "copy/paste html should match"

        return True

    def accept_download(self):
        # check the Firefox UI
        self._driver.set_context("chrome")
        download_button = self._wait.until(
            EC.visibility_of_element_located((By.ID, "downloads-button"))
        )
        download_button.click()
        time.sleep(1)

        download_item = self._wait.until(
            EC.visibility_of_element_located((
                By.CSS_SELECTOR,
                ".download-state .downloadTarget",
            ))
        )
        download_item.click()
        download_name = download_item.get_property("value")

        download_allow = self._wait.until(
            EC.presence_of_element_located((
                By.ID,
                "downloadsPanel-blockedSubview-unblockButton",
            ))
        )
        download_allow.click()

        # back to page
        self._driver.set_context("content")

        return download_name

    def wait_for_download(self):
        # check the Firefox UI
        self._driver.set_context("chrome")
        download_button = self._wait.until(
            EC.visibility_of_element_located((By.ID, "downloads-button"))
        )
        download_button.click()
        time.sleep(1)

        download_item = self._wait.until(
            EC.visibility_of_element_located((
                By.CSS_SELECTOR,
                ".download-state .downloadTarget",
            ))
        )
        download_name = download_item.get_property("value")
        self._logger.info(f"Waiting for download: {download_name}")

        download_progress = self._wait.until(
            EC.presence_of_element_located((
                By.CSS_SELECTOR,
                ".download-state .downloadProgress",
            ))
        )
        self._logger.info(
            "Download progress {}%".format(download_progress.get_property("value"))
        )

        try:
            self._wait.until(lambda d: download_progress.get_property("value") == 100)
        except TimeoutException as ex:
            details_normal = self._wait.until(
                EC.presence_of_element_located((
                    By.CSS_SELECTOR,
                    ".download-state .downloadDetailsNormal",
                ))
            )
            self._logger.info(
                "Download details normal {}".format(
                    details_normal.get_property("value")
                )
            )
            details_hover = self._wait.until(
                EC.presence_of_element_located((
                    By.CSS_SELECTOR,
                    ".download-state .downloadDetailsHover",
                ))
            )
            self._logger.info(
                "Download details hover {}".format(details_hover.get_property("value"))
            )
            raise ex

        # back to page
        self._driver.set_context("content")
        return download_name

    def change_download_folder(self, previous=None, new=None):
        self._logger.info(f"Download change folder: {previous} => {new}")
        self._driver.set_context("chrome")
        self._driver.execute_script(
            "Services.prefs.setIntPref('browser.download.folderList', 2);"
        )
        self._driver.execute_script(
            "Services.prefs.setCharPref('browser.download.dir', arguments[0]);", new
        )
        download_dir_pref = self._driver.execute_script(
            "return Services.prefs.getCharPref('browser.download.dir', null);"
        )
        self._driver.set_context("content")
        self._logger.info(f"Download folder pref: {download_dir_pref}")
        assert download_dir_pref == new, (
            "download directory from pref should match new directory"
        )

    def enable_downloads_debug(self):
        self._driver.set_context("chrome")
        self._logger.info("Setting downloads loglevel to Debug")
        self._driver.execute_script(
            "return Services.prefs.setStringPref('toolkit.download.loglevel', 'Debug');"
        )
        self._driver.set_context("content")

    def open_local(self):
        self.enable_downloads_debug()
        # Start local HTTP server with test file
        server_process, tmpdir = self._start_local_http_server(port=45678)
        # Store server info for cleanup
        self._http_server = server_process
        self._http_tmpdir = tmpdir
        # Open local test page
        download_site = self.open_tab("http://localhost:45678/")
        return download_site

    def get_local_1M(self):
        return self._wait.until(
            EC.presence_of_element_located((
                By.ID,
                "download-link",
            ))
        )

    def test_download_folder_change(self, exp):
        """
        C1756713
        """

        download_site = self.open_local()
        extra_small = self.get_local_1M()
        self._driver.execute_script("arguments[0].click();", extra_small)

        download_name = self.accept_download()
        self.wait_for_download()

        self.open_tab("about:preferences")
        download_folder = self._wait.until(
            EC.presence_of_element_located((By.ID, "chooseFolder"))
        )
        if not download_folder.get_property("value"):
            # Fallback to "downloadFoler" for older Firefox versions
            download_folder = self._wait.until(
                EC.presence_of_element_located((By.ID, "downloadFolder"))
            )
        previous_folder = (
            download_folder
            .get_property("value")
            .replace("\u2066", "")
            .replace("\u2069", "")
        )
        self._logger.info(f"Download folder from about:preferences: {previous_folder}")
        if not os.path.isabs(previous_folder):
            previous_folder = os.path.join(os.environ.get("HOME", ""), previous_folder)
        with tempfile.TemporaryDirectory(
            dir=os.environ.get("HOME"), prefix="snap-test-download"
        ) as tmpdir:
            assert os.path.isdir(tmpdir), "tmpdir download should exists"

            download_1 = os.path.abspath(os.path.join(previous_folder, download_name))
            self._logger.info(f"Download 1 assert: {download_1}")
            assert os.path.isfile(download_1), "downloaded file #1 should exists"

            self.change_download_folder(previous_folder, tmpdir)

            self._driver.switch_to.window(download_site)
            self._driver.execute_script("arguments[0].click();", extra_small)
            self.accept_download()
            download_name2 = self.wait_for_download()
            download_2 = os.path.join(tmpdir, download_name2)

            self._logger.info(f"Download 2 assert: {download_2}")
            assert os.path.isfile(download_2), "downloaded file #2 should exists"

        # Cleanup local HTTP server
        self._stop_local_http_server(self._http_server, self._http_tmpdir)
        self._http_server = None
        self._http_tmpdir = None

        return True

    def test_download_folder_removal(self, exp):
        """
        C1756715
        """

        download_site = self.open_local()
        extra_small = self.get_local_1M()

        with tempfile.TemporaryDirectory(
            dir=os.environ.get("HOME"), prefix="snap-test-download-rm"
        ) as tmpdir:
            self.change_download_folder(None, tmpdir)

            self._driver.switch_to.window(download_site)
            self._driver.execute_script("arguments[0].click();", extra_small)

            self.accept_download()
            download_name = self.wait_for_download()
            download_file = os.path.join(tmpdir, download_name)
            self._logger.info(f"Download assert: {download_file}")
            assert os.path.isdir(tmpdir), "tmpdir download should exists"
            assert os.path.isfile(download_file), "downloaded file should exists"

            self._driver.set_context("chrome")
            download_button = self._wait.until(
                EC.visibility_of_element_located((By.ID, "downloads-button"))
            )
            download_button.click()
            time.sleep(1)

            download_details = self._wait.until(
                EC.visibility_of_element_located((
                    By.CSS_SELECTOR,
                    ".download-state .downloadDetailsNormal",
                ))
            )
            assert download_details.get_property("value").startswith("Completed"), (
                "download should be marked as completed"
            )

        # TemporaryDirectory out of focus so folder removed

        # Close panel we will re-open it
        self._driver.execute_script("this.window.DownloadsButton.hide();")
        self._wait.until(
            EC.invisibility_of_element_located((
                By.CSS_SELECTOR,
                ".download-state .downloadDetailsNormal",
            ))
        )

        assert os.path.isdir(tmpdir) is False, "tmpdir should have been removed"
        assert os.path.isfile(download_file) is False, (
            "downloaded file should have been removed"
        )

        download_button.click()
        time.sleep(1)

        download_item = self._wait.until(
            EC.visibility_of_element_located((
                By.CSS_SELECTOR,
                ".download-state .downloadTarget",
            ))
        )
        download_name_2 = download_item.get_property("value")
        assert download_name == download_name_2, "downloaded names should match"

        download_details = self._wait.until(
            EC.visibility_of_element_located((
                By.CSS_SELECTOR,
                ".download-state .downloadDetailsNormal",
            ))
        )
        assert download_details.get_property("value").startswith(
            "File moved or missing"
        ), "download panel should report file moved/missing"

        self._driver.execute_script("this.window.DownloadsButton.hide();")

        self._driver.set_context("content")

        # Cleanup local HTTP server
        self._stop_local_http_server(self._http_server, self._http_tmpdir)
        self._http_server = None
        self._http_tmpdir = None

        return True


if __name__ == "__main__":
    QATests()
