Skip to content

Commit

Permalink
Add test and doc for changing Firefox browser language and locale
Browse files Browse the repository at this point in the history
Fixes #2361

Signed-off-by: Viet Nguyen Duc <[email protected]>
  • Loading branch information
VietND96 committed Sep 5, 2024
1 parent 3318c3a commit 7e9d497
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 31 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pip-selfcheck.json

# End of https://www.gitignore.io/api/virtualenv
tests/tests/*
tests/target/*

# Created by https://www.gitignore.io/api/python

Expand Down
14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -584,8 +584,13 @@ test_edge_standalone:
;; \
esac

test_firefox:
PLATFORMS=$(PLATFORMS) VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) BASE_RELEASE=$(BASE_RELEASE) BASE_VERSION=$(BASE_VERSION) BINDING_VERSION=$(BINDING_VERSION) SKIP_BUILD=true ./tests/bootstrap.sh NodeFirefox
test_firefox_download_lang_packs:
FIREFOX_VERSION=$$(docker run --rm $(NAME)/node-firefox:$(TAG_VERSION) firefox --version | awk '{print $$3}') ; \
./NodeFirefox/get_lang_package.sh $$FIREFOX_VERSION ./tests/target/firefox_lang_packs

test_firefox: test_firefox_download_lang_packs
PLATFORMS=$(PLATFORMS) VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) BASE_RELEASE=$(BASE_RELEASE) BASE_VERSION=$(BASE_VERSION) BINDING_VERSION=$(BINDING_VERSION) SKIP_BUILD=true \
TEST_FIREFOX_INSTALL_LANG_PACKAGE=true ./tests/bootstrap.sh NodeFirefox

test_firefox_standalone:
PLATFORMS=$(PLATFORMS) VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) BASE_RELEASE=$(BASE_RELEASE) BASE_VERSION=$(BASE_VERSION) BINDING_VERSION=$(BINDING_VERSION) SKIP_BUILD=true ./tests/bootstrap.sh StandaloneFirefox
Expand Down Expand Up @@ -645,6 +650,10 @@ test_video: video hub chrome firefox edge chromium
docker_compose_file=$(or $(DOCKER_COMPOSE_FILE), docker-compose-v3-test-video.yml) ; \
list_of_tests_amd64=$(or $(LIST_OF_TESTS_AMD64), "NodeChrome NodeChromium NodeFirefox NodeEdge") ; \
list_of_tests_arm64=$(or $(LIST_OF_TESTS_ARM64), "NodeChromium NodeFirefox") ; \
TEST_FIREFOX_INSTALL_LANG_PACKAGE=$(or $(TEST_FIREFOX_INSTALL_LANG_PACKAGE), "true") ; \
if [ "$${TEST_FIREFOX_INSTALL_LANG_PACKAGE}" = "true" ]; then \
make test_firefox_download_lang_packs ; \
fi ; \
if [ "$(PLATFORMS)" = "linux/amd64" ]; then \
list_nodes="$${list_of_tests_amd64}" ; \
else \
Expand All @@ -659,6 +668,7 @@ test_video: video hub chrome firefox edge chromium
echo BINDING_VERSION=$(BINDING_VERSION) >> .env ; \
echo TEST_DELAY_AFTER_TEST=$(or $(TEST_DELAY_AFTER_TEST), 2) >> .env ; \
echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=$(or $(SELENIUM_ENABLE_MANAGED_DOWNLOADS), "true") >> .env ; \
echo TEST_FIREFOX_INSTALL_LANG_PACKAGE=$${TEST_FIREFOX_INSTALL_LANG_PACKAGE} >> .env ; \
echo BASIC_AUTH_USERNAME=$(or $(BASIC_AUTH_USERNAME), "admin") >> .env ; \
echo BASIC_AUTH_PASSWORD=$(or $(BASIC_AUTH_PASSWORD), "admin") >> .env ; \
echo SUB_PATH=$(or $(SUB_PATH), "/selenium") >> .env ; \
Expand Down
1 change: 0 additions & 1 deletion NodeBase/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ ARG LANG_WHICH=en
ARG LANG_WHERE=US
ARG ENCODING=UTF-8
ARG LANGUAGE=${LANG_WHICH}_${LANG_WHERE}.${ENCODING}
ARG TARGETARCH

USER root

Expand Down
1 change: 0 additions & 1 deletion NodeChrome/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ USER root
# google-chrome-unstable
#============================================
ARG CHROME_VERSION="google-chrome-stable"
ARG TARGETARCH
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor | tee /etc/apt/trusted.gpg.d/google.gpg >/dev/null \
&& echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list \
&& apt-get update -qqy \
Expand Down
1 change: 0 additions & 1 deletion NodeEdge/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ USER root
# e.g. microsoft-edge-beta=88.0.692.0-1
#============================================
ARG EDGE_VERSION="microsoft-edge-stable"
ARG TARGETARCH
RUN wget -q -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/microsoft.gpg >/dev/null \
&& echo "deb https://packages.microsoft.com/repos/edge stable main" >> /etc/apt/sources.list.d/microsoft-edge.list \
&& apt-get update -qqy \
Expand Down
27 changes: 15 additions & 12 deletions NodeFirefox/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ RUN if [ "$(dpkg --print-architecture)" = "amd64" ]; then \
FIREFOX_DOWNLOAD_URL="https://download.mozilla.org/?product=firefox-nightly-latest-ssl&os=linux64-aarch64&lang=en-US" ; \
fi \
&& apt-get update -qqy \
&& apt-get -qqy --no-install-recommends install libavcodec-extra \
libgtk-3-dev libdbus-glib-1-dev \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/* \
&& apt-get -qqy --no-install-recommends install libavcodec-extra libgtk-3-dev libdbus-glib-1-dev \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/* ${HOME}/firefox \
&& wget --no-verbose -O /tmp/firefox.tar.bz2 $FIREFOX_DOWNLOAD_URL \
&& rm -rf /opt/firefox \
&& tar -C /opt -xjf /tmp/firefox.tar.bz2 \
&& tar -C ${HOME} -xjf /tmp/firefox.tar.bz2 \
&& rm /tmp/firefox.tar.bz2 \
&& mv /opt/firefox /opt/firefox-$FIREFOX_VERSION \
&& ln -fs /opt/firefox-$FIREFOX_VERSION/firefox /usr/bin/firefox
&& mkdir -p ${HOME}/firefox/distribution/extensions \
&& setfacl -Rm u:${SEL_USER}:rwx ${HOME}/firefox \
&& setfacl -Rm g:${SEL_GROUP}:rwx ${HOME}/firefox \
&& ln -fs ${HOME}/firefox/firefox /usr/bin/firefox

#============
# GeckoDriver
Expand All @@ -46,16 +46,19 @@ RUN LATEST_VERSION=$(curl -sk https://api.github.com/repos/mozilla/geckodriver/r
#============================================
# Firefox cleanup script and supervisord file
#============================================
COPY firefox-cleanup.sh /opt/bin/firefox-cleanup.sh
COPY firefox-cleanup.conf /etc/supervisor/conf.d/firefox-cleanup.conf
COPY --chown="${SEL_UID}:${SEL_GID}" firefox-cleanup.sh get_lang_package.sh /opt/bin/
COPY --chown="${SEL_UID}:${SEL_GID}" firefox-cleanup.conf /etc/supervisor/conf.d/firefox-cleanup.conf
RUN chmod +x /opt/bin/firefox-cleanup.sh /opt/bin/get_lang_package.sh

USER ${SEL_UID}

#============================================
# Dumping Browser information for config
#============================================
RUN echo "firefox" > /opt/selenium/browser_name
RUN firefox --version | awk '{print $3}' > /opt/selenium/browser_version
RUN echo "\"moz:firefoxOptions\": {\"binary\": \"/usr/bin/firefox\"}" > /opt/selenium/browser_binary_location
RUN echo "firefox" > /opt/selenium/browser_name \
&& firefox --version | awk '{print $3}' > /opt/selenium/browser_version \
&& echo "\"moz:firefoxOptions\": {\"binary\": \"/usr/bin/firefox\"}" > /opt/selenium/browser_binary_location \
# Download the language pack for Firefox
&& /opt/bin/get_lang_package.sh

ENV SE_OTEL_SERVICE_NAME="selenium-node-firefox"
37 changes: 37 additions & 0 deletions NodeFirefox/get_lang_package.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash

function on_exit() {
local exit_code=$?
rm -f /tmp/xpi_files.txt
exit $exit_code
}
trap on_exit EXIT ERR

# Script is used to download language packs for a specific version of Firefox.
# It requires the version number as the first argument and the target directory as the second argument.

VERSION=${1:-$(firefox --version | awk '{print $3}')}
TARGET_DIR="${2:-$(dirname $(readlink -f $(which firefox)))/distribution/extensions}"
BASE_URL="https://ftp.mozilla.org/pub/firefox/releases/$VERSION/linux-x86_64/xpi/"

# Create target directory if it doesn't exist
mkdir -p "${TARGET_DIR}"

# Download the list of files
wget -q -O - "${BASE_URL}" | grep -oP '(?<=href=")[^"]*.xpi' >/tmp/xpi_files.txt

echo "Downloading language packs for Firefox version $VERSION to $TARGET_DIR ..."

# Loop through each file and download it
while IFS= read -r file; do
file=$(basename "${file}")
echo "Downloading "${BASE_URL}${file}" ..."
curl -sk -o "${TARGET_DIR}/${file}" "${BASE_URL}${file}"
target_file="${TARGET_DIR}/langpack-${file%.xpi}@firefox.mozilla.org.xpi"
mv "${TARGET_DIR}/${file}" "${target_file}"
if [ -f "${target_file}" ]; then
echo "Downloaded ${target_file}"
fi
done </tmp/xpi_files.txt

echo "All language packs are downloaded to $TARGET_DIR"
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Talk to us at https://www.selenium.dev/support/
* [Automatic browser leftovers cleanup](#automatic-browser-leftovers-cleanup)
* [Mask sensitive information in console logs](#mask-sensitive-information-in-console-logs)
* [Secure Connection](#secure-connection)
* [Browser language and locale](#browser-language-and-locale)
* [Building the images](#building-the-images)
* [Build the images with specific versions](#build-the-images-with-specific-versions)
* [Upgrade browser version in the images](#upgrade-browser-version-in-the-images)
Expand Down Expand Up @@ -1278,6 +1279,36 @@ The self-signed certificate also needs to be trusted by the client (add to syste

Refer to sample: [`docker-compose-v3-full-grid-secure.yml`](docker-compose-v3-full-grid-secure.yml)

## Browser language and locale

Different browsers have different ways to set the language and locale from binding.

### Firefox

Firefox can be configured to use a specific language and locale by setting the profile preference when create WebDriver from binding. In addition, language pack need to be installed as add-on for browser UI language to take effect. For example, to set the browser language and locale to `vi-VN`, you can use the following steps:

Get the latest Firefox language pack for the desired language e.g. https://download.mozilla.org/?product=firefox-langpack-latest-SSL&lang=vi. Then, you can install the language pack as an add-on when creating the RemoteWebDriver instance.

```python
profile = webdriver.FirefoxProfile()
profile.set_preference('intl.accept_languages', 'vi-VN,vi')
profile.set_preference('intl.locale.requested', 'vi-VN,vi')
options = FirefoxOptions()
options.profile = profile
driver = webdriver.Remote(options=options, command_executor="http://selenium-hub:4444/wd/hub")
webdriver.Firefox.install_addon(driver, "/local/path/to/vi.xpi")
driver.get('https://google.com')
```

There is a [script](NodeFirefox/get_lang_package.sh) to get all available language packs for a given Firefox version. You can run the script to get the language packs to your source. For example:

```bash
FIREFOX_VERSION=$(docker run --rm --entrypoint="" selenium/node-firefox:latest firefox --version | awk '{print $3}') \
&& ./NodeFirefox/get_lang_package.sh ${FIREFOX_VERSION} /local/path/to/download
```

Or, you can mount the container directory `/home/seluser/firefox/distribution/extensions` to host directory to access packs were pre-built in the container for using in your test script.

___

## Building the images
Expand Down
17 changes: 17 additions & 0 deletions tests/SeleniumTests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
TEST_NODE_RELAY = os.environ.get('TEST_NODE_RELAY', 'false')
TEST_ANDROID_PLATFORM_API = os.environ.get('ANDROID_PLATFORM_API')
TEST_PLATFORMS = os.environ.get('TEST_PLATFORMS', 'linux/amd64')
TEST_FIREFOX_INSTALL_LANG_PACKAGE = os.environ.get('TEST_FIREFOX_INSTALL_LANG_PACKAGE', 'false').lower() == 'true'

if SELENIUM_GRID_USERNAME and SELENIUM_GRID_PASSWORD:
SELENIUM_GRID_HOST = f"{SELENIUM_GRID_USERNAME}:{SELENIUM_GRID_PASSWORD}@{SELENIUM_GRID_HOST}"
Expand Down Expand Up @@ -187,6 +188,8 @@ def setUp(self):
profile = webdriver.FirefoxProfile()
profile.set_preference("browser.download.manager.showWhenStarting", False)
profile.set_preference("browser.helperApps.neverAsk.saveToDisk", "*/*")
profile.set_preference('intl.accept_languages', 'vi-VN,vi')
profile.set_preference('intl.locale.requested', 'vi-VN,vi')
options = FirefoxOptions()
options.profile = profile
options.enable_downloads = SELENIUM_ENABLE_MANAGED_DOWNLOADS
Expand All @@ -212,6 +215,20 @@ def test_title_and_maximize_window(self):
self.driver.maximize_window()
self.assertTrue(self.driver.title == 'The Internet')

def test_accept_languages(self):
if TEST_FIREFOX_INSTALL_LANG_PACKAGE:
addon_id = webdriver.Firefox.install_addon(self.driver, "./target/firefox_lang_packs/[email protected]")
self.driver.get('https://gtranslate.io/detect-browser-language')
wait = WebDriverWait(self.driver, WEB_DRIVER_WAIT_TIMEOUT)
lang_code = wait.until(
EC.presence_of_element_located((By.XPATH, '(//*[@class="notranslate"])[1]'))
)
self.driver.execute_script("arguments[0].scrollIntoView();", lang_code)
self.assertTrue(lang_code.text == 'vi-VN', "Language code should be vi-VN")
time.sleep(1)
self.driver.get('https://google.com')
time.sleep(2)

class Autoscaling():
def run(self, test_classes):
with concurrent.futures.ThreadPoolExecutor() as executor:
Expand Down
1 change: 1 addition & 0 deletions tests/docker-compose-v3-test-standalone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ services:
- BINDING_VERSION=${BINDING_VERSION}
- TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST}
- SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS}
- TEST_FIREFOX_INSTALL_LANG_PACKAGE=${TEST_FIREFOX_INSTALL_LANG_PACKAGE}
command: ["/bin/bash", "-c", "./bootstrap.sh ${NODE}"]
1 change: 1 addition & 0 deletions tests/docker-compose-v3-test-video.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ services:
- BINDING_VERSION=${BINDING_VERSION}
- TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST}
- SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS}
- TEST_FIREFOX_INSTALL_LANG_PACKAGE=${TEST_FIREFOX_INSTALL_LANG_PACKAGE}
command: ["/bin/bash", "-c", "./bootstrap.sh ${NODE}"]
39 changes: 25 additions & 14 deletions tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,34 @@
import unittest
import re
import platform
import signal

import docker
from docker.errors import NotFound

def clean_up():
logger.info("Cleaning up...")

test_container = client.containers.get(test_container_id)
test_container.kill()
test_container.remove()

if standalone:
logger.info("Standalone Cleaned up")
else:
# Kill the launched hub
hub = client.containers.get(hub_id)
hub.kill()
hub.remove()
logger.info("Hub / Node Cleaned up")

def signal_handler(signum, frame):
clean_up()
sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGQUIT, signal_handler)

# LOGGING #
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -272,20 +296,7 @@ def standalone_browser_container_matches(container):

# Avoiding a container cleanup if tests run inside docker compose
if not run_in_docker_compose:
logger.info("Cleaning up...")

test_container = client.containers.get(test_container_id)
test_container.kill()
test_container.remove()

if standalone:
logger.info("Standalone Cleaned up")
else:
# Kill the launched hub
hub = client.containers.get(hub_id)
hub.kill()
hub.remove()
logger.info("Hub / Node Cleaned up")
clean_up()

if failed:
exit(1)

0 comments on commit 7e9d497

Please sign in to comment.