summaryrefslogtreecommitdiffstats
path: root/taskcluster/docker/snap-coreXX-build
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /taskcluster/docker/snap-coreXX-build
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'taskcluster/docker/snap-coreXX-build')
-rw-r--r--taskcluster/docker/snap-coreXX-build/Dockerfile80
-rwxr-xr-xtaskcluster/docker/snap-coreXX-build/install-snap.sh24
-rw-r--r--taskcluster/docker/snap-coreXX-build/parse.py39
-rwxr-xr-xtaskcluster/docker/snap-coreXX-build/run.sh114
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/README.md24
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/basic_tests.py307
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/basic_tests/expectations.json.in14
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests.py860
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/qa_expectations.json98
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_custom_fonts_ref.pngbin0 -> 51060 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_download_base.pngbin0 -> 127756 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_base.pngbin0 -> 167492 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_documentProperties.pngbin0 -> 177679 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_down.pngbin0 -> 242142 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_end.pngbin0 -> 135099 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_firstPage.pngbin0 -> 194764 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_hand_tool.pngbin0 -> 197844 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_home.pngbin0 -> 194764 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_lastPage.pngbin0 -> 135099 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_left.pngbin0 -> 194878 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_next.pngbin0 -> 247191 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageRotateCcw.pngbin0 -> 194810 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageRotateCw.pngbin0 -> 276055 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pagedown.pngbin0 -> 216369 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageup.pngbin0 -> 171383 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_previous.pngbin0 -> 224991 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_right.pngbin0 -> 247234 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_select_text.pngbin0 -> 195176 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_up.pngbin0 -> 224891 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_100p.pngbin0 -> 194764 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_150p.pngbin0 -> 166658 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_400p.pngbin0 -> 72156 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_50p.pngbin0 -> 150887 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_75p.pngbin0 -> 155313 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_actual.pngbin0 -> 195201 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_fit.pngbin0 -> 162736 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_width.pngbin0 -> 188823 bytes
-rw-r--r--taskcluster/docker/snap-coreXX-build/snap-tests/requirements.txt22
-rwxr-xr-xtaskcluster/docker/snap-coreXX-build/snap-tests/tests.sh68
-rwxr-xr-xtaskcluster/docker/snap-coreXX-build/snap-tests/update-references.sh22
40 files changed, 1672 insertions, 0 deletions
diff --git a/taskcluster/docker/snap-coreXX-build/Dockerfile b/taskcluster/docker/snap-coreXX-build/Dockerfile
new file mode 100644
index 0000000000..29a343f6ea
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/Dockerfile
@@ -0,0 +1,80 @@
+FROM $DOCKER_IMAGE_PARENT
+MAINTAINER Gabriele Svelto <gsvelto@mozilla.com>
+
+RUN mkdir -p /builds
+RUN id worker || useradd -d /builds/worker -s /bin/bash -m worker
+WORKDIR /builds/worker
+
+# We need to declare all potentially cache volumes as caches. Also,
+# making high I/O paths volumes increase I/O throughput because of
+# AUFS slowness.
+VOLUME /builds/worker/checkouts
+
+RUN apt-get update && \
+ apt-get install -y \
+ build-essential \
+ curl \
+ libavcodec58 \
+ libavutil56 \
+ jq \
+ patch \
+ patchelf \
+ python3-dev \
+ python3-yaml \
+ squashfs-tools \
+ tar \
+ unzip \
+ uuid \
+ wget \
+ zip
+
+ARG SNAP_BASE
+
+COPY install-snap.sh /usr/local/bin/
+
+RUN install-snap.sh core
+
+RUN install-snap.sh core20
+
+# Snapcraft snap depends on core20 for python3.8 even when we target core22
+RUN install-snap.sh snapcraft
+
+RUN install-snap.sh core22
+
+ARG SNAP_LIST
+
+RUN for snap in $SNAP_LIST; do install-snap.sh "${snap}"; done
+
+# Create a snapcraft runner
+RUN mkdir -p /snap/bin
+RUN echo "#!/bin/sh" > /snap/bin/snapcraft
+RUN snap_version="$(awk '/^version:/{print $2}' /snap/snapcraft/current/meta/snap.yaml)" && echo "export SNAP_VERSION=\"$snap_version\"\nexport PATH=\$SNAP/bin/:\$SNAP/libexec/snapcraft/:\$PATH" >> /snap/bin/snapcraft
+RUN echo 'exec "$SNAP/bin/snapcraft" "$@"' >> /snap/bin/snapcraft
+RUN chmod +x /snap/bin/snapcraft
+
+# Generate locale
+RUN apt update && apt dist-upgrade --yes && apt install --yes sudo snapd locales && locale-gen en_US.UTF-8 && apt-get autoremove -y
+
+# Set the proper environment
+ENV LANG="en_US.UTF-8"
+ENV LANGUAGE="en_US:en"
+ENV LC_ALL="en_US.UTF-8"
+ENV PATH="/snap/bin:$PATH"
+ENV SNAP="/snap/snapcraft/current"
+ENV SNAP_NAME="snapcraft"
+ENV SNAP_ARCH="amd64"
+
+# Snapcraft pull will need to sudo apt-get update ...
+RUN echo "worker ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/worker && \
+ chmod 0440 /etc/sudoers.d/worker
+
+COPY run.sh /builds/worker/run.sh
+
+COPY parse.py /builds/worker/parse.py
+
+COPY patches /builds/worker/patches/
+
+RUN chown -R worker:worker /builds/worker
+
+# Set a default command useful for debugging
+CMD ["/bin/bash", "--login"]
diff --git a/taskcluster/docker/snap-coreXX-build/install-snap.sh b/taskcluster/docker/snap-coreXX-build/install-snap.sh
new file mode 100755
index 0000000000..66f64d088d
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/install-snap.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+set -ex
+
+SNAP_TO_INSTALL=$1
+
+if [ -z "${SNAP_TO_INSTALL}" ]; then
+ echo "Please give a snap name"
+ exit 1
+fi
+
+# Grab the requested snap from the stable channel and unpack it in the proper
+# place (the 'Snap-CDN: none' header allows building in restricted network
+# environments such as Launchpad builders)
+
+# shellcheck disable=SC2046
+curl -L \
+ -H 'Snap-CDN: none' \
+ $(curl -H 'X-Ubuntu-Series: 16' "https://api.snapcraft.io/api/v1/snaps/details/${SNAP_TO_INSTALL}?channel=stable" | jq '.download_url' -r) \
+ --output "${SNAP_TO_INSTALL}.snap"
+
+mkdir -p "/snap/${SNAP_TO_INSTALL}"
+
+unsquashfs -d "/snap/${SNAP_TO_INSTALL}/current" "${SNAP_TO_INSTALL}.snap"
diff --git a/taskcluster/docker/snap-coreXX-build/parse.py b/taskcluster/docker/snap-coreXX-build/parse.py
new file mode 100644
index 0000000000..82e0e2f4a6
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/parse.py
@@ -0,0 +1,39 @@
+#!/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 http://mozilla.org/MPL/2.0/.
+
+
+import sys
+
+import yaml
+
+
+def has_pkg_section(p, section):
+ has_section = section in p.keys()
+ if has_section:
+ for pkg in p[section]:
+ yield pkg
+
+
+def iter_pkgs(part, all_pkgs):
+ for pkg in has_pkg_section(part, "build-packages"):
+ if pkg not in all_pkgs:
+ all_pkgs.append(pkg)
+ for pkg in has_pkg_section(part, "stage-packages"):
+ if pkg not in all_pkgs:
+ all_pkgs.append(pkg)
+
+
+def parse(yaml_file):
+ all_pkgs = []
+ with open(yaml_file, "r") as inp:
+ snap = yaml.safe_load(inp)
+ parts = snap["parts"]
+ for p in parts:
+ iter_pkgs(parts[p], all_pkgs)
+ return " ".join(all_pkgs)
+
+
+if __name__ == "__main__":
+ print(parse(sys.argv[1]))
diff --git a/taskcluster/docker/snap-coreXX-build/run.sh b/taskcluster/docker/snap-coreXX-build/run.sh
new file mode 100755
index 0000000000..5b3efbb756
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/run.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+
+set -ex
+
+mkdir -p /builds/worker/artifacts/
+mkdir -p /builds/worker/.local/state/snapcraft/
+ln -s /builds/worker/artifacts /builds/worker/.local/state/snapcraft/log
+
+BRANCH=$1
+DEBUG=${2:-0}
+
+export LC_ALL=C.UTF-8
+export LANG=C.UTF-8
+export SNAP_ARCH=amd64
+export SNAPCRAFT_BUILD_INFO=1
+
+export PATH=$PATH:$HOME/.local/bin/
+unset MOZ_AUTOMATION
+
+MOZCONFIG=mozconfig.in
+
+USE_SNAP_FROM_STORE=${USE_SNAP_FROM_STORE:-0}
+
+TRY=0
+if [ "${BRANCH}" = "try" ]; then
+ BRANCH=nightly
+ TRY=1
+fi
+
+if [ "${USE_SNAP_FROM_STORE}" = "0" ]; then
+ # ESR currently still has a hard dependency against zstandard==0.17.0 so
+ # install this specific version here
+ if [ "${BRANCH}" = "esr" ]; then
+ sudo apt-get remove -y python3-zstandard && sudo apt-get install -y python3-pip && sudo pip3 install --no-input zstandard==0.17.0
+ MOZCONFIG=mozconfig
+ fi
+
+ # Stable and beta runs out of file descriptors during link with gold
+ ulimit -n 65536
+
+ git clone --single-branch --depth 1 --branch "${BRANCH}" https://github.com/canonical/firefox-snap/
+ cd firefox-snap/
+
+ if [ "${TRY}" = "1" ]; then
+ # Symlink so that we can directly re-use Gecko mercurial checkout
+ ln -s /builds/worker/checkouts/gecko gecko
+ fi
+
+ # Force an update to avoid the case of a stale docker image and repos updated
+ # after
+ sudo apt-get update
+
+ # shellcheck disable=SC2046
+ sudo apt-get install -y $(/usr/bin/python3 /builds/worker/parse.py snapcraft.yaml)
+
+ # CRAFT_PARTS_PACKAGE_REFRESH required to avoid snapcraft running apt-get update
+ # especially for stage-packages
+ if [ -d "/builds/worker/patches/${BRANCH}/" ]; then
+ for p in /builds/worker/patches/"${BRANCH}"/*.patch; do
+ patch -p1 < "$p"
+ done;
+ fi
+
+ if [ "${TRY}" = "1" ]; then
+ # don't remove hg source, and don't force changeset so we get correct stamp
+ # still force repo because the try clone is from mozilla-unified but the
+ # generated link does not work
+ sed -ri 's|rm -rf .hg||g' snapcraft.yaml
+ # shellcheck disable=SC2016
+ sed -ri 's|MOZ_SOURCE_REPO=\$\{REPO\}|MOZ_SOURCE_REPO=${GECKO_HEAD_REPOSITORY}|g' snapcraft.yaml
+ # shellcheck disable=SC2016
+ sed -ri 's|MOZ_SOURCE_CHANGESET=\$\{REVISION\}|MOZ_SOURCE_CHANGESET=${GECKO_HEAD_REV}|g' snapcraft.yaml
+ # shellcheck disable=SC2016
+ sed -ri 's|hg clone --stream \$REPO -u \$REVISION|cp -r \$SNAPCRAFT_PROJECT_DIR/gecko/. |g' snapcraft.yaml
+ fi
+
+ if [ "${DEBUG}" = "1" ]; then
+ {
+ echo "ac_add_options --enable-debug"
+ echo "ac_add_options --disable-install-strip"
+ } >> ${MOZCONFIG}
+ echo "MOZ_DEBUG=1" >> ${MOZCONFIG}
+
+ # No PGO on debug builds
+ sed -ri 's/ac_add_options --enable-linker=gold//g' snapcraft.yaml
+ sed -ri 's/ac_add_options --enable-lto=cross//g' snapcraft.yaml
+ sed -ri 's/ac_add_options MOZ_PGO=1//g' snapcraft.yaml
+ fi
+
+ SNAPCRAFT_BUILD_ENVIRONMENT_MEMORY=64G \
+ SNAPCRAFT_BUILD_ENVIRONMENT_CPU=$(nproc) \
+ CRAFT_PARTS_PACKAGE_REFRESH=0 \
+ snapcraft --destructive-mode --verbose
+else
+ mkdir from-snap-store && cd from-snap-store
+
+ CHANNEL="${BRANCH}"
+ if [ "${CHANNEL}" = "try" ] || [ "${CHANNEL}" = "nightly" ]; then
+ CHANNEL=edge
+ fi;
+
+ snap download --channel="${CHANNEL}" firefox
+ SNAP_DEBUG_NAME=$(find . -maxdepth 1 -type f -name "firefox*.snap" | sed -e 's/\.snap$/.debug/g')
+ touch "${SNAP_DEBUG_NAME}"
+fi
+
+cp ./*.snap ./*.debug /builds/worker/artifacts/
+
+# Those are for fetches usage by the test task
+cp ./*.snap /builds/worker/artifacts/firefox.snap
+cp ./*.debug /builds/worker/artifacts/firefox.debug
+
+# Those are for running snap-upstream-test
+cd /builds/worker/checkouts/gecko/taskcluster/docker/snap-coreXX-build/snap-tests/ && zip -r9 /builds/worker/artifacts/snap-tests.zip ./*
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/README.md b/taskcluster/docker/snap-coreXX-build/snap-tests/README.md
new file mode 100644
index 0000000000..73f78c72bc
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/README.md
@@ -0,0 +1,24 @@
+Debugging tests
+================
+
+You can use the `TEST_FILTER` environment variable, e.g., `TEST_FILTER=xxx`
+will filter test named `test_xxx`.
+
+Setting `TEST_GECKODRIVER_TRACE` to any value will make Selenium dump a trace
+log for debugging.
+
+You can control running headless or not with `TEST_NO_HEADLESS`. Currently,
+the copy/paste image test required NOT to run headless.
+
+More useful for local repro, you can set `TEST_NO_QUIT` if you need to keep
+inspecting the browser at the end of a test.
+
+Data URL containing the diff screenshot will be dumped to stdout/stderr when
+`TEST_DUMP_DIFF` is set in the environment.
+
+Updating reference screenshots
+==============================
+ - `./mach try fuzzy --push-to-lando --full --env TEST_COLLECT_REFERENCE=1 -q "'snap-upstream-test"`
+ - note the successfull task id you want to source
+ - you need curl and jq installed
+ - ./update-references.sh <TASK_ID>
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/basic_tests.py b/taskcluster/docker/snap-coreXX-build/snap-tests/basic_tests.py
new file mode 100644
index 0000000000..fb8841817b
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/basic_tests.py
@@ -0,0 +1,307 @@
+#!/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 base64
+import io
+import json
+import os
+import sys
+import time
+import traceback
+
+from mozlog import formatters, handlers, structuredlog
+from PIL import Image, ImageChops
+from selenium import webdriver
+from selenium.common.exceptions import TimeoutException
+from selenium.webdriver.common.by import By
+from selenium.webdriver.firefox.options import Options
+from selenium.webdriver.firefox.service import Service
+from selenium.webdriver.remote.webelement import WebElement
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+class SnapTestsBase:
+ def __init__(self, exp):
+ driver_service = Service(
+ executable_path=r"/snap/firefox/current/usr/lib/firefox/geckodriver",
+ log_output=os.path.join(
+ os.environ.get("ARTIFACT_DIR", ""), "geckodriver.log"
+ ),
+ )
+ options = Options()
+ if "TEST_GECKODRIVER_TRACE" in os.environ.keys():
+ options.log.level = "trace"
+ options.binary_location = r"/snap/firefox/current/usr/lib/firefox/firefox"
+ if not "TEST_NO_HEADLESS" in os.environ.keys():
+ options.add_argument("--headless")
+ if "MOZ_AUTOMATION" in os.environ.keys():
+ os.environ["MOZ_LOG_FILE"] = os.path.join(
+ os.environ.get("ARTIFACT_DIR"), "gecko.log"
+ )
+ self._driver = webdriver.Firefox(service=driver_service, options=options)
+
+ self._logger = structuredlog.StructuredLogger(self.__class__.__name__)
+ self._logger.add_handler(
+ handlers.StreamHandler(sys.stdout, formatters.TbplFormatter())
+ )
+
+ test_filter = "test_{}".format(os.environ.get("TEST_FILTER", ""))
+ object_methods = [
+ method_name
+ for method_name in dir(self)
+ if callable(getattr(self, method_name))
+ and method_name.startswith(test_filter)
+ ]
+
+ self._logger.suite_start(object_methods)
+
+ assert self._dir is not None
+
+ self._wait = WebDriverWait(self._driver, self.get_timeout())
+ self._longwait = WebDriverWait(self._driver, 60)
+
+ with open(exp, "r") as j:
+ self._expectations = json.load(j)
+
+ rv = False
+ try:
+ first_tab = self._driver.window_handles[0]
+ for m in object_methods:
+ tabs_before = set(self._driver.window_handles)
+ self._driver.switch_to.window(first_tab)
+ self._logger.test_start(m)
+ rv = getattr(self, m)(self._expectations[m])
+ self._driver.switch_to.parent_frame()
+ if rv:
+ self._logger.test_end(m, status="OK")
+ else:
+ self._logger.test_end(m, status="FAIL")
+ tabs_after = set(self._driver.window_handles)
+ tabs_opened = tabs_after - tabs_before
+ self._logger.info("opened {} tabs".format(len(tabs_opened)))
+ for tab in tabs_opened:
+ self._driver.switch_to.window(tab)
+ self._driver.close()
+ self._wait.until(EC.number_of_windows_to_be(len(tabs_before)))
+ except Exception as ex:
+ rv = False
+ test_status = "ERROR"
+ if isinstance(ex, AssertionError):
+ test_status = "FAIL"
+ elif isinstance(ex, TimeoutException):
+ test_status = "TIMEOUT"
+
+ test_message = repr(ex)
+ self.save_screenshot("screenshot_{}.png".format(test_status.lower()))
+ self._driver.switch_to.parent_frame()
+ self.save_screenshot("screenshot_{}_parent.png".format(test_status.lower()))
+ self._logger.test_end(m, status=test_status, message=test_message)
+ traceback.print_exc()
+ finally:
+ self._driver.switch_to.window(first_tab)
+
+ if not "TEST_NO_QUIT" in os.environ.keys():
+ self._driver.quit()
+
+ self._logger.info("Exiting with {}".format(rv))
+ self._logger.suite_end()
+ sys.exit(0 if rv is True else 1)
+
+ def get_screenshot_destination(self, name):
+ final_name = name
+ if "MOZ_AUTOMATION" in os.environ.keys():
+ final_name = os.path.join(os.environ.get("ARTIFACT_DIR"), name)
+ return final_name
+
+ def save_screenshot(self, name):
+ final_name = self.get_screenshot_destination(name)
+ self._logger.info("Saving screenshot '{}' to '{}'".format(name, final_name))
+ self._driver.save_screenshot(final_name)
+
+ def get_timeout(self):
+ if "TEST_TIMEOUT" in os.environ.keys():
+ return int(os.getenv("TEST_TIMEOUT"))
+ else:
+ return 5
+
+ def maybe_collect_reference(self):
+ return "TEST_COLLECT_REFERENCE" in os.environ.keys()
+
+ def open_tab(self, url):
+ opened_tabs = len(self._driver.window_handles)
+
+ self._driver.switch_to.new_window("tab")
+ self._wait.until(EC.number_of_windows_to_be(opened_tabs + 1))
+ self._driver.get(url)
+
+ return self._driver.current_window_handle
+
+ def assert_rendering(self, exp, element_or_driver):
+ # wait a bit for things to settle down
+ time.sleep(0.5)
+
+ # Convert as RGB otherwise we cannot get difference
+ png_bytes = (
+ element_or_driver.screenshot_as_png
+ if isinstance(element_or_driver, WebElement)
+ else element_or_driver.get_screenshot_as_png()
+ )
+ svg_png = Image.open(io.BytesIO(png_bytes)).convert("RGB")
+ svg_png_cropped = svg_png.crop((0, 0, svg_png.width - 20, svg_png.height - 20))
+
+ if self.maybe_collect_reference():
+ new_ref = "new_{}".format(exp["reference"])
+ new_ref_file = self.get_screenshot_destination(new_ref)
+ self._logger.info(
+ "Collecting new reference screenshot: {} => {}".format(
+ new_ref, new_ref_file
+ )
+ )
+
+ with open(new_ref_file, "wb") as current_screenshot:
+ svg_png_cropped.save(current_screenshot)
+
+ return
+
+ svg_ref = Image.open(os.path.join(self._dir, exp["reference"])).convert("RGB")
+ diff = ImageChops.difference(svg_ref, svg_png_cropped)
+
+ if diff.getbbox() is not None:
+ buffered = io.BytesIO()
+ diff.save(buffered, format="PNG")
+
+ if "TEST_DUMP_DIFF" in os.environ.keys():
+ diff_b64 = base64.b64encode(buffered.getvalue())
+ self._logger.info(
+ "data:image/png;base64,{}".format(diff_b64.decode("utf-8"))
+ )
+
+ with open(
+ self.get_screenshot_destination("differences.png"), "wb"
+ ) as diff_screenshot:
+ diff_screenshot.write(buffered.getvalue())
+
+ with open(
+ self.get_screenshot_destination("current_rendering.png"), "wb"
+ ) as current_screenshot:
+ svg_png_cropped.save(current_screenshot)
+
+ assert diff.getbbox() is None, "Mismatching screenshots for {}".format(
+ exp["reference"]
+ )
+
+
+class SnapTests(SnapTestsBase):
+ def __init__(self, exp):
+ self._dir = "basic_tests"
+ super(SnapTests, self).__init__(exp)
+
+ def test_about_support(self, exp):
+ self.open_tab("about:support")
+
+ version_box = self._wait.until(
+ EC.visibility_of_element_located((By.ID, "version-box"))
+ )
+ self._wait.until(lambda d: len(version_box.text) > 0)
+ self._logger.info("about:support version: {}".format(version_box.text))
+ assert version_box.text == exp["version_box"], "version text should match"
+
+ distributionid_box = self._wait.until(
+ EC.visibility_of_element_located((By.ID, "distributionid-box"))
+ )
+ self._wait.until(lambda d: len(distributionid_box.text) > 0)
+ self._logger.info(
+ "about:support distribution ID: {}".format(distributionid_box.text)
+ )
+ assert (
+ distributionid_box.text == exp["distribution_id"]
+ ), "distribution_id should match"
+
+ windowing_protocol = self._driver.execute_script(
+ "return document.querySelector('th[data-l10n-id=\"graphics-window-protocol\"').parentNode.lastChild.textContent;"
+ )
+ self._logger.info(
+ "about:support windowing protocol: {}".format(windowing_protocol)
+ )
+ assert windowing_protocol == "wayland", "windowing protocol should be wayland"
+
+ return True
+
+ def test_about_buildconfig(self, exp):
+ self.open_tab("about:buildconfig")
+
+ source_link = self._wait.until(
+ EC.visibility_of_element_located((By.CSS_SELECTOR, "a"))
+ )
+ self._wait.until(lambda d: len(source_link.text) > 0)
+ self._logger.info("about:buildconfig source: {}".format(source_link.text))
+ assert source_link.text.startswith(
+ exp["source_repo"]
+ ), "source repo should exists and match"
+
+ build_flags_box = self._wait.until(
+ EC.visibility_of_element_located((By.CSS_SELECTOR, "p:last-child"))
+ )
+ self._wait.until(lambda d: len(build_flags_box.text) > 0)
+ self._logger.info("about:support buildflags: {}".format(build_flags_box.text))
+ assert (
+ build_flags_box.text.find(exp["official"]) >= 0
+ ), "official build flag should be there"
+
+ return True
+
+ def test_youtube(self, exp):
+ self.open_tab("https://www.youtube.com")
+
+ # Wait for the consent dialog and accept it
+ self._logger.info("Wait for consent form")
+ try:
+ self._wait.until(
+ EC.visibility_of_element_located(
+ (By.CSS_SELECTOR, "button[aria-label*=Accept]")
+ )
+ ).click()
+ except TimeoutException:
+ self._logger.info("Wait for consent form: timed out, maybe it is not here")
+
+ try:
+ # Find first video and click it
+ self._logger.info("Wait for one video")
+ self._wait.until(
+ EC.visibility_of_element_located((By.ID, "video-title-link"))
+ ).click()
+ except TimeoutException:
+ # We might have got the "try searching to get started"
+ # link to News channel
+ self._driver.get("https://www.youtube.com/channel/UCYfdidRxbB8Qhf0Nx7ioOYw")
+ self._logger.info("Wait again for one video")
+ self._wait.until(
+ EC.visibility_of_element_located((By.ID, "video-title-link"))
+ ).click()
+
+ # Wait for duration to be set to something
+ self._logger.info("Wait for video to start")
+ video = self._wait.until(
+ EC.visibility_of_element_located((By.CLASS_NAME, "html5-main-video"))
+ )
+ self._wait.until(lambda d: type(video.get_property("duration")) == float)
+ self._logger.info("video duration: {}".format(video.get_property("duration")))
+ assert (
+ video.get_property("duration") > exp["duration"]
+ ), "youtube video should have duration"
+
+ self._wait.until(lambda d: video.get_property("currentTime") > exp["playback"])
+ self._logger.info("video played: {}".format(video.get_property("currentTime")))
+ assert (
+ video.get_property("currentTime") > exp["playback"]
+ ), "youtube video should perform playback"
+
+ return True
+
+
+if __name__ == "__main__":
+ SnapTests(exp=sys.argv[1])
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/basic_tests/expectations.json.in b/taskcluster/docker/snap-coreXX-build/snap-tests/basic_tests/expectations.json.in
new file mode 100644
index 0000000000..92e26dd699
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/basic_tests/expectations.json.in
@@ -0,0 +1,14 @@
+{
+ "test_about_support": {
+ "version_box": "#RUNTIME_VERSION#",
+ "distribution_id": "canonical-002"
+ },
+ "test_about_buildconfig": {
+ "source_repo": "https://hg.mozilla.org/",
+ "official": "MOZILLA_OFFICIAL=1"
+ },
+ "test_youtube": {
+ "duration": 1,
+ "playback": 2
+ }
+}
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests.py b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests.py
new file mode 100644
index 0000000000..781000981e
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests.py
@@ -0,0 +1,860 @@
+#!/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 tempfile
+import time
+
+from basic_tests import SnapTestsBase
+from selenium.common.exceptions import StaleElementReferenceException, 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"
+
+ super(QATests, self).__init__(
+ exp=os.path.join(self._dir, "qa_expectations.json")
+ )
+
+ def _test_audio_playback(
+ self, url, iframe_selector=None, click_to_play=False, video_selector=None
+ ):
+ self._logger.info("open url {}".format(url))
+ if url:
+ self.open_tab(url)
+
+ if iframe_selector:
+ self._logger.info("find iframe")
+ iframe = self._driver.find_element(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")) == 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 click_to_play:
+ self._driver.execute_script("arguments[0].click();", video)
+ 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 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")
+ if click_to_play:
+ video.send_keys("k")
+ else:
+ self._driver.execute_script("arguments[0].pause()", video)
+ datum = video.get_property("currentTime")
+ time.sleep(1)
+ datum_after_sleep = video.get_property("currentTime")
+ self._logger.info(
+ "datum={} datum_after_sleep={}".format(datum, datum_after_sleep)
+ )
+ assert datum == datum_after_sleep, "<video> is sleeping"
+ assert video.get_property("paused") is True, "<video> is paused"
+
+ self._logger.info("find video: unpause")
+ # unpause and verify playback
+ if click_to_play:
+ video.send_keys("k")
+ else:
+ self._driver.execute_script("arguments[0].play()", video)
+ assert video.get_property("paused") is False, "<video> is not paused"
+ time.sleep(2)
+ datum_after_resume = video.get_property("currentTime")
+ self._logger.info(
+ "datum_after_resume={} datum_after_sleep={}".format(
+ datum_after_resume, 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
+ )
+ assert (
+ video.get_property("volume") == ref_volume * 0.25
+ ), "<video> sound volume increased"
+
+ self._logger.info("find video: done")
+
+ def _test_audio_video_playback(self, url):
+ self._logger.info("open url {}".format(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="#drive-viewer-video-player-object-0",
+ click_to_play=True,
+ )
+
+ self._logger.info("find video again")
+ # we are still in the iframe
+ video = self._wait.until(
+ EC.visibility_of_element_located((By.CSS_SELECTOR, "video"))
+ )
+ self._logger.info("try fullscreen")
+ promise = self._driver.execute_script(
+ "return arguments[0].requestFullscreen().then(() => { return true; }).catch(() => { return false; })",
+ video,
+ )
+ assert promise is True, "<video> full screen promised fullfilled"
+
+ 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/0BwxFVkl63-lEY3l3ODJReDg3RzQ/view?resourcekey=0-5kDw2QbFk9eLrWE1N9M1rQ"
+ )
+
+ 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(
+ "http://www.hyperion-records.co.uk/audiotest/18%20MacCunn%20The%20Lay%20of%20the%20Last%20Minstrel%20-%20Part%202%20Final%20chorus%20O%20Caledonia!%20stern%20and%20wild.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, "div.page[data-page-number='{}'] canvas".format(page))
+ )
+ )
+
+ try:
+ self._wait.until(lambda d: page.is_displayed() is True)
+ except StaleElementReferenceException as ex:
+ self._logger.info("Stale element but who cares?: {}".format(ex))
+ time.sleep(2)
+
+ # self._wait.until(
+ # lambda d: d.execute_script(
+ # 'return window.getComputedStyle(document.querySelector(".loadingInput.start"), "::after").getPropertyValue("visibility");'
+ # )
+ # == "hidden"
+ # )
+
+ # PDF.js can take time to settle and we don't have a nice way to wait
+ # for an event on it
+ time.sleep(2)
+ 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("{}".format(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")
+ page_1 = self.pdf_get_page(1)
+ self.assert_rendering(exp["base"], page_1)
+
+ # 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("assert {}".format(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("reset to page for {}".format(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("click menu for {}".format(menu_id))
+ # open menu
+ secondary_menu.click()
+
+ self._logger.info("find button for {}".format(menu_id))
+ button_to_test = self._wait.until(
+ EC.visibility_of_element_located((By.ID, menu_id))
+ )
+
+ self._logger.info("click button for {}".format(menu_id))
+ button_to_test.click()
+
+ # rotation does not close the menu?:
+ if menu_id == "pageRotateCw" or menu_id == "pageRotateCcw":
+ secondary_menu.click()
+
+ time.sleep(0.2)
+
+ self._logger.info("assert {}".format(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()
+ self.assert_rendering(exp["select_text"], 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)
+ self._logger.info("assert {}".format(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
+ ), "type {} should/should ({}) not be in clipboard".format(
+ mime_type, should_be_present
+ )
+
+ 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)
+
+ blocked_item = self._wait.until(
+ EC.visibility_of_element_located(
+ (By.CSS_SELECTOR, ".download-state .downloadTarget")
+ )
+ )
+ blocked_item.click()
+ download_name = blocked_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")
+
+ download_progress = self._wait.until(
+ EC.presence_of_element_located(
+ (By.CSS_SELECTOR, ".download-state .downloadProgress")
+ )
+ )
+ self._wait.until(lambda d: download_progress.get_property("value") == 100)
+
+ # back to page
+ self._driver.set_context("content")
+ return download_name
+
+ def change_download_folder(self, previous=None, new=None):
+ self._logger.info("Download change folder: {} => {}".format(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("Download folder pref: {}".format(download_dir_pref))
+ assert (
+ download_dir_pref == new
+ ), "download directory from pref should match new directory"
+
+ def open_thinkbroadband(self):
+ download_site = self.open_tab("https://www.thinkbroadband.com/download")
+ try:
+ consent = self._wait.until(
+ EC.visibility_of_element_located(
+ (By.CSS_SELECTOR, ".t-acceptAllButton")
+ )
+ )
+ consent.click()
+ except TimeoutException:
+ self._logger.info("Wait for consent form: timed out, maybe it is not here")
+ return download_site
+
+ def test_download_folder_change(self, exp):
+ """
+ C1756713
+ """
+
+ download_site = self.open_thinkbroadband()
+ extra_small = self._wait.until(
+ EC.presence_of_element_located(
+ (
+ By.CSS_SELECTOR,
+ "div.module:nth-child(8) > p:nth-child(1) > a:nth-child(1)",
+ )
+ )
+ )
+ 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, "downloadFolder"))
+ )
+ previous_folder = (
+ download_folder.get_property("value")
+ .replace("\u2066", "")
+ .replace("\u2069", "")
+ )
+ self._logger.info(
+ "Download folder from about:preferences: {}".format(previous_folder)
+ )
+ if not os.path.isabs(previous_folder):
+ previous_folder = os.path.join(os.environ.get("HOME", ""), previous_folder)
+ with tempfile.TemporaryDirectory() 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("Download 1 assert: {}".format(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("Download 2 assert: {}".format(download_2))
+ assert os.path.isfile(download_2), "downloaded file #2 should exists"
+
+ return True
+
+ def test_download_folder_removal(self, exp):
+ """
+ C1756715
+ """
+
+ download_site = self.open_thinkbroadband()
+ extra_small = self._wait.until(
+ EC.presence_of_element_located(
+ (
+ By.CSS_SELECTOR,
+ "div.module:nth-child(8) > p:nth-child(1) > a:nth-child(1)",
+ )
+ )
+ )
+
+ with tempfile.TemporaryDirectory() 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("Download assert: {}".format(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")
+
+ return True
+
+
+if __name__ == "__main__":
+ QATests()
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/qa_expectations.json b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/qa_expectations.json
new file mode 100644
index 0000000000..f9806ffa90
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/qa_expectations.json
@@ -0,0 +1,98 @@
+{
+ "test_custom_fonts": {
+ "reference": "test_custom_fonts_ref.png"
+ },
+ "test_h264_mov": {},
+ "test_he_aac": {},
+ "test_flac": {},
+ "test_mp3": {},
+ "test_ogg": {},
+ "test_pdf_navigation": {
+ "base": {
+ "reference": "test_pdf_navigation_base.png"
+ },
+ "next": {
+ "reference": "test_pdf_navigation_next.png"
+ },
+ "previous": {
+ "reference": "test_pdf_navigation_previous.png"
+ },
+ "down": {
+ "reference": "test_pdf_navigation_down.png"
+ },
+ "up": {
+ "reference": "test_pdf_navigation_up.png"
+ },
+ "right": {
+ "reference": "test_pdf_navigation_right.png"
+ },
+ "left": {
+ "reference": "test_pdf_navigation_left.png"
+ },
+ "pagedown": {
+ "reference": "test_pdf_navigation_pagedown.png"
+ },
+ "pageup": {
+ "reference": "test_pdf_navigation_pageup.png"
+ },
+ "end": {
+ "reference": "test_pdf_navigation_end.png"
+ },
+ "home": {
+ "reference": "test_pdf_navigation_home.png"
+ },
+ "firstPage": {
+ "reference": "test_pdf_navigation_firstPage.png"
+ },
+ "lastPage": {
+ "reference": "test_pdf_navigation_lastPage.png"
+ },
+ "pageRotateCw": {
+ "reference": "test_pdf_navigation_pageRotateCw.png"
+ },
+ "pageRotateCcw": {
+ "reference": "test_pdf_navigation_pageRotateCcw.png"
+ },
+ "documentProperties": {
+ "reference": "test_pdf_navigation_documentProperties.png"
+ },
+ "select_text": {
+ "reference": "test_pdf_navigation_select_text.png"
+ },
+ "hand_tool": {
+ "reference": "test_pdf_navigation_hand_tool.png"
+ }
+ },
+ "test_pdf_zoom": {
+ "p1_50p": {
+ "reference": "test_pdf_zoom_p1_50p.png"
+ },
+ "p1_75p": {
+ "reference": "test_pdf_zoom_p1_75p.png"
+ },
+ "p1_100p": {
+ "reference": "test_pdf_zoom_p1_100p.png"
+ },
+ "p1_150p": {
+ "reference": "test_pdf_zoom_p1_150p.png"
+ },
+ "p1_400p": {
+ "reference": "test_pdf_zoom_p1_400p.png"
+ },
+ "p1_actual": {
+ "reference": "test_pdf_zoom_p1_actual.png"
+ },
+ "p1_fit": {
+ "reference": "test_pdf_zoom_p1_fit.png"
+ },
+ "p1_width": {
+ "reference": "test_pdf_zoom_p1_width.png"
+ }
+ },
+ "test_pdf_download": {
+ "reference": "test_pdf_download_base.png"
+ },
+ "test_copy_paste_image_text": {},
+ "test_download_folder_change": {},
+ "test_download_folder_removal": {}
+}
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_custom_fonts_ref.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_custom_fonts_ref.png
new file mode 100644
index 0000000000..02263329e2
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_custom_fonts_ref.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_download_base.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_download_base.png
new file mode 100644
index 0000000000..98bf604c4b
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_download_base.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_base.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_base.png
new file mode 100644
index 0000000000..71046ad3c3
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_base.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_documentProperties.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_documentProperties.png
new file mode 100644
index 0000000000..6eeeb95f2b
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_documentProperties.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_down.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_down.png
new file mode 100644
index 0000000000..c3818c9726
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_down.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_end.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_end.png
new file mode 100644
index 0000000000..86bf1f158d
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_end.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_firstPage.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_firstPage.png
new file mode 100644
index 0000000000..1b33d8374e
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_firstPage.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_hand_tool.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_hand_tool.png
new file mode 100644
index 0000000000..d84c9158d9
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_hand_tool.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_home.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_home.png
new file mode 100644
index 0000000000..1b33d8374e
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_home.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_lastPage.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_lastPage.png
new file mode 100644
index 0000000000..86bf1f158d
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_lastPage.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_left.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_left.png
new file mode 100644
index 0000000000..aa36bda00c
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_left.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_next.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_next.png
new file mode 100644
index 0000000000..21e930607c
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_next.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageRotateCcw.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageRotateCcw.png
new file mode 100644
index 0000000000..19062697d9
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageRotateCcw.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageRotateCw.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageRotateCw.png
new file mode 100644
index 0000000000..176ab1dcff
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageRotateCw.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pagedown.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pagedown.png
new file mode 100644
index 0000000000..db05b7feb2
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pagedown.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageup.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageup.png
new file mode 100644
index 0000000000..8df2181704
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_pageup.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_previous.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_previous.png
new file mode 100644
index 0000000000..19c727c5e0
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_previous.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_right.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_right.png
new file mode 100644
index 0000000000..be6d48cc30
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_right.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_select_text.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_select_text.png
new file mode 100644
index 0000000000..95fa211e1b
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_select_text.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_up.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_up.png
new file mode 100644
index 0000000000..cd5067574c
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_navigation_up.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_100p.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_100p.png
new file mode 100644
index 0000000000..1b33d8374e
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_100p.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_150p.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_150p.png
new file mode 100644
index 0000000000..add7d3a9bb
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_150p.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_400p.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_400p.png
new file mode 100644
index 0000000000..58f832c3e5
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_400p.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_50p.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_50p.png
new file mode 100644
index 0000000000..310bb889be
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_50p.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_75p.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_75p.png
new file mode 100644
index 0000000000..5a21ade0c0
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_75p.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_actual.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_actual.png
new file mode 100644
index 0000000000..d047a4fcbf
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_actual.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_fit.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_fit.png
new file mode 100644
index 0000000000..b721fd3296
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_fit.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_width.png b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_width.png
new file mode 100644
index 0000000000..061eafda7f
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/qa_tests/test_pdf_zoom_p1_width.png
Binary files differ
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/requirements.txt b/taskcluster/docker/snap-coreXX-build/snap-tests/requirements.txt
new file mode 100644
index 0000000000..df0967738e
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/requirements.txt
@@ -0,0 +1,22 @@
+attrs==23.1.0
+blessed==1.20.0
+certifi==2023.7.22
+exceptiongroup==1.1.3
+h11==0.14.0
+idna==3.4
+mozfile==3.0.0
+mozlog==8.0.0
+mozterm==1.0.0
+outcome==1.2.0
+Pillow==10.1.0
+PySocks==1.7.1
+PyYAML==6.0.1
+selenium==4.12.0
+six==1.16.0
+sniffio==1.3.0
+sortedcontainers==2.4.0
+trio==0.22.2
+trio-websocket==0.10.4
+urllib3==2.0.5
+wcwidth==0.2.13
+wsproto==1.2.0
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/tests.sh b/taskcluster/docker/snap-coreXX-build/snap-tests/tests.sh
new file mode 100755
index 0000000000..854ef2c9af
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/tests.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+
+set -ex
+
+pwd
+
+SUITE=${1:-basic}
+SUITE=${SUITE//--/}
+
+export ARTIFACT_DIR=$TASKCLUSTER_ROOT_DIR/builds/worker/artifacts/
+mkdir -p "$ARTIFACT_DIR"
+
+# There's a bug in snapd ~2.60.3-2.61 that will make "snap refresh" fail
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1873359
+# https://bugs.launchpad.net/snapd/+bug/2048104/comments/13
+#
+# So we retrieve the version and we will allow the first "snap refresh" to
+# fail for versions < 2.61.1
+SNAP_VERSION=$(snap info snapd --color=never --unicode=never |grep "installed:" | awk '{ print $2 }')
+SNAP_MAJOR=$(echo "${SNAP_VERSION}" | cut -d'.' -f1)
+SNAP_MINOR=$(echo "${SNAP_VERSION}" | cut -d'.' -f2)
+SNAP_RELEASE=$(echo "${SNAP_VERSION}" | cut -d'.' -f3)
+
+REFRESH_CAN_FAIL=true
+if [ "${SNAP_MAJOR}" -ge 2 ]; then
+ if [ "${SNAP_MAJOR}" -gt 2 ]; then
+ REFRESH_CAN_FAIL=false
+ else
+ if [ "${SNAP_MINOR}" -ge 61 ]; then
+ if [ "${SNAP_MINOR}" -gt 61 ]; then
+ REFRESH_CAN_FAIL=false
+ else
+ if [ "${SNAP_RELEASE}" -gt 0 ]; then
+ REFRESH_CAN_FAIL=false
+ fi
+ fi
+ fi
+ fi
+fi
+
+if [ "${REFRESH_CAN_FAIL}" = "true" ]; then
+ sudo snap refresh || true
+else
+ sudo snap refresh
+fi;
+
+sudo snap refresh --hold=24h firefox
+
+sudo snap install --name firefox --dangerous ./firefox.snap
+
+RUNTIME_VERSION=$(snap run firefox --version | awk '{ print $3 }')
+
+python3 -m pip install --user -r requirements.txt
+
+# Required otherwise copy/paste does not work
+# Bug 1878643
+export TEST_NO_HEADLESS=1
+
+if [ -n "${MOZ_LOG}" ]; then
+ export MOZ_LOG_FILE="${ARTIFACT_DIR}/gecko-log"
+fi
+
+if [ "${SUITE}" = "basic" ]; then
+ sed -e "s/#RUNTIME_VERSION#/${RUNTIME_VERSION}/#" < basic_tests/expectations.json.in > basic_tests/expectations.json
+ python3 basic_tests.py basic_tests/expectations.json
+else
+ python3 "${SUITE}"_tests.py
+fi;
diff --git a/taskcluster/docker/snap-coreXX-build/snap-tests/update-references.sh b/taskcluster/docker/snap-coreXX-build/snap-tests/update-references.sh
new file mode 100755
index 0000000000..d8ce620aff
--- /dev/null
+++ b/taskcluster/docker/snap-coreXX-build/snap-tests/update-references.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+TASK_ID=${1}
+THIS="$(dirname "$0")"
+
+if [ -z "${TASK_ID}" ]; then
+ echo "Please provide a task ID"
+ exit 1
+fi
+
+TASKCLUSTER_API_ROOT="https://firefox-ci-tc.services.mozilla.com"
+ARTIFACTS="${TASKCLUSTER_API_ROOT}/api/queue/v1/task/${TASK_ID}/artifacts"
+
+for reference in $(curl "${ARTIFACTS}" | jq -r '.artifacts | . [] | select(.name | contains("public/build/new_")) | .name');
+do
+ name="$(basename "${reference}")"
+ final_name=${name//new_/}
+ target_name=$(find "${THIS}" -type f -name "${final_name}")
+ url="${TASKCLUSTER_API_ROOT}/api/queue/v1/task/${TASK_ID}/artifacts/${reference}"
+ echo "$url => $target_name"
+ curl -SL "${url}" -o "${target_name}"
+done;