ci: Switch to using geckodriver (#6541)

* Switch to using geckodriver

* Switch to selenium 4

* Undo

* Remove comment

* Fixes

* Restore non-standard line endings to minimize diff

* Undo

* Remove comment

* test: Fix test_upcoming_view_time_zone_selection

The inputs are hidden and managed by JS, so click
the visible elements instead.

* test: Clumsy fix to test_upcoming_materials_modal

Waiting for the button to be clickable does not
work because the modal is still fading in, so does
not actually close. Would be better to check for it
responding, but I haven't found the right way to do
that yet.

* test: Fix test_add_author_forms

Sending '\n' does not seem to work as it did before,
so click the option instead.

Also reverted some fixme hacks that seem to be obe.

* ci: Update base.Dockerfile

* test: add resource limits to dev/tests/debug.sh env

* ci: add upload of geckodriver.log on failure

* ci: run tests as user 1001

* ci: run app-create-dirs as sudo

* ci: set home folder to root to run tests

---------

Co-authored-by: Jennifer Richards <jennifer@staff.ietf.org>
Co-authored-by: Nicolas Giard <github@ngpixel.com>
Co-authored-by: Robert Sparks <rjsparks@nostrum.com>
This commit is contained in:
Lars Eggert 2023-11-21 23:30:50 +02:00 committed by GitHub
parent 377db9d091
commit 3d44825333
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 66 additions and 81 deletions

View file

@ -45,12 +45,19 @@ jobs:
echo "Running tests..." echo "Running tests..."
if [[ "x${{ github.event.inputs.ignoreLowerCoverage }}" == "xtrue" ]]; then if [[ "x${{ github.event.inputs.ignoreLowerCoverage }}" == "xtrue" ]]; then
echo "Lower coverage failures will be ignored." echo "Lower coverage failures will be ignored."
./ietf/manage.py test -v2 --validate-html-harder --settings=settings_test --ignore-lower-coverage HOME=/root ./ietf/manage.py test -v2 --validate-html-harder --settings=settings_test --ignore-lower-coverage
else else
./ietf/manage.py test -v2 --validate-html-harder --settings=settings_test HOME=/root ./ietf/manage.py test -v2 --validate-html-harder --settings=settings_test
fi fi
coverage xml coverage xml
- name: Upload geckodriver.log
uses: actions/upload-artifact@v3
if: ${{ failure() }}
with:
name: geckodriverlog
path: geckodriver.log
- name: Upload Coverage Results to Codecov - name: Upload Coverage Results to Codecov
uses: codecov/codecov-action@v3.1.4 uses: codecov/codecov-action@v3.1.4
with: with:

View file

@ -12,7 +12,7 @@ echo "Fetching latest images..."
docker pull ghcr.io/ietf-tools/datatracker-app-base:latest docker pull ghcr.io/ietf-tools/datatracker-app-base:latest
docker pull ghcr.io/ietf-tools/datatracker-db:latest docker pull ghcr.io/ietf-tools/datatracker-db:latest
echo "Starting containers..." echo "Starting containers..."
docker compose -f docker-compose.debug.yml -p dtdebug up -d docker compose -f docker-compose.debug.yml -p dtdebug --compatibility up -d
echo "Copying working directory into container..." echo "Copying working directory into container..."
docker compose -p dtdebug cp ../../. app:/__w/datatracker/datatracker/ docker compose -p dtdebug cp ../../. app:/__w/datatracker/datatracker/
echo "Run prepare script..." echo "Run prepare script..."

View file

@ -16,6 +16,12 @@ services:
CI: 'true' CI: 'true'
GITHUB_ACTIONS: 'true' GITHUB_ACTIONS: 'true'
HOME: /github/home HOME: /github/home
deploy:
resources:
limits:
cpus: '2'
memory: '7GB'
db: db:
image: ghcr.io/ietf-tools/datatracker-db:latest image: ghcr.io/ietf-tools/datatracker-db:latest
restart: unless-stopped restart: unless-stopped

View file

@ -83,15 +83,6 @@ RUN apt-get update --fix-missing && apt-get install -qy --no-install-recommends
# Install kramdown-rfc2629 (ruby) # Install kramdown-rfc2629 (ruby)
RUN gem install kramdown-rfc2629 RUN gem install kramdown-rfc2629
# Install chromedriver
COPY docker/scripts/app-install-chromedriver.sh /tmp/app-install-chromedriver.sh
RUN sed -i 's/\r$//' /tmp/app-install-chromedriver.sh && \
chmod +x /tmp/app-install-chromedriver.sh
RUN /tmp/app-install-chromedriver.sh
# Fix /dev/shm permissions for chromedriver
RUN chmod 1777 /dev/shm
# GeckoDriver # GeckoDriver
ARG GECKODRIVER_VERSION=latest ARG GECKODRIVER_VERSION=latest
RUN GK_VERSION=$(if [ ${GECKODRIVER_VERSION:-latest} = "latest" ]; then echo "0.33.0"; else echo $GECKODRIVER_VERSION; fi) \ RUN GK_VERSION=$(if [ ${GECKODRIVER_VERSION:-latest} = "latest" ]; then echo "0.33.0"; else echo $GECKODRIVER_VERSION; fi) \

View file

@ -18,9 +18,6 @@ sudo chown -R dev:dev "$WORKSPACEDIR/.vite"
sudo chown -R dev:dev "$WORKSPACEDIR/.yarn/unplugged" sudo chown -R dev:dev "$WORKSPACEDIR/.yarn/unplugged"
sudo chown dev:dev "/assets" sudo chown dev:dev "/assets"
echo "Fix chromedriver /dev/shm permissions..."
sudo chmod 1777 /dev/shm
# Run nginx # Run nginx
echo "Starting nginx..." echo "Starting nginx..."
sudo nginx sudo nginx

View file

@ -1,18 +0,0 @@
#!/bin/bash
HOSTARCH=$(arch)
if [ $HOSTARCH == "x86_64" ]; then
echo "Installing chrome driver..."
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list
apt-get update -y
apt-get install -y google-chrome-stable
CHROMEVER=$(google-chrome --product-version | grep -o "[^\.]*\.[^\.]*\.[^\.]*")
DRIVERVER=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROMEVER")
wget -q --continue -P /chromedriver "http://chromedriver.storage.googleapis.com/$DRIVERVER/chromedriver_linux64.zip"
unzip /chromedriver/chromedriver* -d /chromedriver
ln -s /chromedriver/chromedriver /usr/local/bin/chromedriver
ln -s /chromedriver/chromedriver /usr/bin/chromedriver
else
echo "This architecture doesn't support chromedriver. Skipping installation..."
fi

View file

@ -41,7 +41,7 @@ class EditAuthorsTests(IetfSeleniumTestCase):
(By.CSS_SELECTOR, result_selector), (By.CSS_SELECTOR, result_selector),
name name
)) ))
input.send_keys('\n') # select the object self.driver.find_element(By.CSS_SELECTOR, result_selector).click()
# After the author is selected, the email select options will be populated. # After the author is selected, the email select options will be populated.
# Wait for that, then click on the option corresponding to the requested email. # Wait for that, then click on the option corresponding to the requested email.
@ -94,10 +94,8 @@ class EditAuthorsTests(IetfSeleniumTestCase):
# get the "add author" button so we can add blank author forms # get the "add author" button so we can add blank author forms
add_author_button = self.driver.find_element(By.ID, 'add-author-button') add_author_button = self.driver.find_element(By.ID, 'add-author-button')
for index, auth in enumerate(authors): for index, auth in enumerate(authors):
self.driver.execute_script("arguments[0].scrollIntoView();", add_author_button) # FIXME: no idea why this fails: self.scroll_to_element(add_author_button) # Can only click if it's in view!
# self.scroll_to_element(add_author_button) # Can only click if it's in view! add_author_button.click() # Create a new form. Automatically scrolls to it.
self.driver.execute_script("arguments[0].click();", add_author_button) # FIXME: no idea why this fails:
# add_author_button.click() # Create a new form. Automatically scrolls to it.
author_forms = authors_list.find_elements(By.CLASS_NAME, 'author-panel') author_forms = authors_list.find_elements(By.CLASS_NAME, 'author-panel')
authors_added = index + 1 authors_added = index + 1
self.assertEqual(len(author_forms), authors_added + 1) # Started with 1 author, hence +1 self.assertEqual(len(author_forms), authors_added + 1) # Started with 1 author, hence +1
@ -119,9 +117,8 @@ class EditAuthorsTests(IetfSeleniumTestCase):
self.driver.find_element(By.ID, 'id_basis').send_keys('change testing') self.driver.find_element(By.ID, 'id_basis').send_keys('change testing')
# Now click the 'submit' button and check that the update was accepted. # Now click the 'submit' button and check that the update was accepted.
submit_button = self.driver.find_element(By.CSS_SELECTOR, '#content button[type="submit"]') submit_button = self.driver.find_element(By.CSS_SELECTOR, '#content button[type="submit"]')
self.driver.execute_script("arguments[0].click();", submit_button) # FIXME: no idea why this fails: self.scroll_to_element(submit_button)
# self.scroll_to_element(submit_button) submit_button.click()
# submit_button.click()
# Wait for redirect to the document_main view # Wait for redirect to the document_main view
self.wait.until( self.wait.until(
expected_conditions.url_to_be( expected_conditions.url_to_be(

View file

@ -499,7 +499,7 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
clicked_index = 1 clicked_index = 1
# scroll so the button we want to click is just below the navbar, otherwise it may # scroll so the button we want to click is just below the navbar, otherwise it may
# fall beneath the sessions panel # fall beneath the sessions panel
navbar = self.driver.find_element_by_class_name('navbar') navbar = self.driver.find_element(By.CSS_SELECTOR, '.navbar')
self.driver.execute_script( self.driver.execute_script(
'window.scrollBy({top: %s, behavior: "instant"})' % ( 'window.scrollBy({top: %s, behavior: "instant"})' % (
future_swap_days_buttons[1].location['y'] - navbar.size['height'] future_swap_days_buttons[1].location['y'] - navbar.size['height']
@ -1232,10 +1232,13 @@ class InterimTests(IetfSeleniumTestCase):
self.driver.get(self.absreverse('ietf.meeting.views.upcoming')) self.driver.get(self.absreverse('ietf.meeting.views.upcoming'))
tz_select_input = self.driver.find_element(By.ID, 'timezone-select') tz_select_input = self.driver.find_element(By.ID, 'timezone-select')
tz_select_bottom_input = self.driver.find_element(By.ID, 'timezone-select-bottom') tz_select_bottom_input = self.driver.find_element(By.ID, 'timezone-select-bottom')
local_tz_link = self.driver.find_element(By.ID, 'local-timezone')
utc_tz_link = self.driver.find_element(By.ID, 'utc-timezone') # For things we click, need to click the labels / actually visible items. The actual inputs are hidden
local_tz_bottom_link = self.driver.find_element(By.ID, 'local-timezone-bottom') # and managed by the JS.
utc_tz_bottom_link = self.driver.find_element(By.ID, 'utc-timezone-bottom') local_tz_link = self.driver.find_element(By.CSS_SELECTOR, 'label[for="local-timezone"]')
utc_tz_link = self.driver.find_element(By.CSS_SELECTOR, 'label[for="utc-timezone"]')
local_tz_bottom_link = self.driver.find_element(By.CSS_SELECTOR, 'label[for="local-timezone-bottom"]')
utc_tz_bottom_link = self.driver.find_element(By.CSS_SELECTOR, 'label[for="utc-timezone-bottom"]')
# wait for the select box to be updated - look for an arbitrary time zone to be in # wait for the select box to be updated - look for an arbitrary time zone to be in
# its options list to detect this # its options list to detect this
@ -1245,6 +1248,9 @@ class InterimTests(IetfSeleniumTestCase):
(By.CSS_SELECTOR, '#timezone-select > option[value="%s"]' % arbitrary_tz) (By.CSS_SELECTOR, '#timezone-select > option[value="%s"]' % arbitrary_tz)
) )
) )
tz_selector_clickables = self.driver.find_elements(By.CSS_SELECTOR, ".tz-display .select2")
self.assertEqual(len(tz_selector_clickables), 2)
(tz_selector_top, tz_selector_bottom) = tz_selector_clickables
arbitrary_tz_bottom_opt = tz_select_bottom_input.find_element(By.CSS_SELECTOR, arbitrary_tz_bottom_opt = tz_select_bottom_input.find_element(By.CSS_SELECTOR,
'#timezone-select-bottom > option[value="%s"]' % arbitrary_tz) '#timezone-select-bottom > option[value="%s"]' % arbitrary_tz)
@ -1266,8 +1272,7 @@ class InterimTests(IetfSeleniumTestCase):
_assert_ietf_tz_correct(ietf_meetings, local_tz) _assert_ietf_tz_correct(ietf_meetings, local_tz)
# click 'utc' button # click 'utc' button
self.driver.execute_script("arguments[0].click();", utc_tz_link) # FIXME-LARS: not working: utc_tz_link.click()
# utc_tz_link.click()
self.wait.until(expected_conditions.element_to_be_selected(utc_tz_opt)) self.wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.assertFalse(local_tz_opt.is_selected()) self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected()) self.assertFalse(local_tz_bottom_opt.is_selected())
@ -1279,8 +1284,7 @@ class InterimTests(IetfSeleniumTestCase):
_assert_ietf_tz_correct(ietf_meetings, 'UTC') _assert_ietf_tz_correct(ietf_meetings, 'UTC')
# click back to 'local' # click back to 'local'
self.driver.execute_script("arguments[0].click();", local_tz_link) # FIXME-LARS: not working: local_tz_link.click()
# local_tz_link.click()
self.wait.until(expected_conditions.element_to_be_selected(local_tz_opt)) self.wait.until(expected_conditions.element_to_be_selected(local_tz_opt))
self.assertTrue(local_tz_opt.is_selected()) self.assertTrue(local_tz_opt.is_selected())
self.assertTrue(local_tz_bottom_opt.is_selected()) self.assertTrue(local_tz_bottom_opt.is_selected())
@ -1292,7 +1296,12 @@ class InterimTests(IetfSeleniumTestCase):
_assert_ietf_tz_correct(ietf_meetings, local_tz) _assert_ietf_tz_correct(ietf_meetings, local_tz)
# Now select a different item from the select input # Now select a different item from the select input
arbitrary_tz_opt.click() tz_selector_top.click()
self.wait.until(
expected_conditions.presence_of_element_located(
(By.CSS_SELECTOR, 'span.select2-container .select2-results li[id$="America/Halifax"]')
)
).click()
self.wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt)) self.wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt))
self.assertFalse(local_tz_opt.is_selected()) self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected()) self.assertFalse(local_tz_bottom_opt.is_selected())
@ -1305,8 +1314,8 @@ class InterimTests(IetfSeleniumTestCase):
# Now repeat those tests using the widgets at the bottom of the page # Now repeat those tests using the widgets at the bottom of the page
# click 'utc' button # click 'utc' button
self.driver.execute_script("arguments[0].click();", utc_tz_bottom_link) # FIXME-LARS: not working: self.scroll_to_element(utc_tz_bottom_link)
# utc_tz_bottom_link.click() utc_tz_bottom_link.click()
self.wait.until(expected_conditions.element_to_be_selected(utc_tz_opt)) self.wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.assertFalse(local_tz_opt.is_selected()) self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected()) self.assertFalse(local_tz_bottom_opt.is_selected())
@ -1318,8 +1327,8 @@ class InterimTests(IetfSeleniumTestCase):
_assert_ietf_tz_correct(ietf_meetings, 'UTC') _assert_ietf_tz_correct(ietf_meetings, 'UTC')
# click back to 'local' # click back to 'local'
self.driver.execute_script("arguments[0].click();", local_tz_bottom_link) # FIXME-LARS: not working: self.scroll_to_element(local_tz_bottom_link)
# local_tz_bottom_link.click() local_tz_bottom_link.click()
self.wait.until(expected_conditions.element_to_be_selected(local_tz_opt)) self.wait.until(expected_conditions.element_to_be_selected(local_tz_opt))
self.assertTrue(local_tz_opt.is_selected()) self.assertTrue(local_tz_opt.is_selected())
self.assertTrue(local_tz_bottom_opt.is_selected()) self.assertTrue(local_tz_bottom_opt.is_selected())
@ -1331,7 +1340,13 @@ class InterimTests(IetfSeleniumTestCase):
_assert_ietf_tz_correct(ietf_meetings, local_tz) _assert_ietf_tz_correct(ietf_meetings, local_tz)
# Now select a different item from the select input # Now select a different item from the select input
arbitrary_tz_bottom_opt.click() self.scroll_to_element(tz_selector_bottom)
tz_selector_bottom.click()
self.wait.until(
expected_conditions.presence_of_element_located(
(By.CSS_SELECTOR, 'span.select2-container .select2-results li[id$="America/Halifax"]')
)
).click()
self.wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt)) self.wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt))
self.assertFalse(local_tz_opt.is_selected()) self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected()) self.assertFalse(local_tz_bottom_opt.is_selected())
@ -1382,6 +1397,7 @@ class InterimTests(IetfSeleniumTestCase):
), ),
'Modal close button not found or not clickable', 'Modal close button not found or not clickable',
) )
time.sleep(0.3) # gross, but the button is clickable while still fading in
close_modal_button.click() close_modal_button.click()
self.wait.until( self.wait.until(
expected_conditions.invisibility_of_element(modal_div), expected_conditions.invisibility_of_element(modal_div),

View file

@ -572,8 +572,6 @@ GLOBAL_TEST_FIXTURES = [ 'names','ietf.utils.test_data.make_immutable_base_data'
TEST_DIFF_FAILURE_DIR = "/tmp/test/failure/" TEST_DIFF_FAILURE_DIR = "/tmp/test/failure/"
TEST_GHOSTDRIVER_LOG_PATH = "ghostdriver.log"
# These are regexes # These are regexes
TEST_URL_COVERAGE_EXCLUDE = [ TEST_URL_COVERAGE_EXCLUDE = [
r"^\^admin/", r"^\^admin/",

View file

@ -1,7 +1,8 @@
# Copyright The IETF Trust 2014-2021, All Rights Reserved # Copyright The IETF Trust 2014-2021, All Rights Reserved
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.conf import settings import os
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
from unittest import skipIf from unittest import skipIf
@ -9,10 +10,9 @@ skip_selenium = False
skip_message = "" skip_message = ""
try: try:
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.chrome.service import Service from selenium.webdriver.firefox.service import Service
from selenium.webdriver.chrome.options import Options from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
except ImportError as e: except ImportError as e:
skip_selenium = True skip_selenium = True
skip_message = "Skipping selenium tests: %s" % e skip_message = "Skipping selenium tests: %s" % e
@ -21,7 +21,7 @@ except ImportError as e:
from ietf.utils.pipe import pipe from ietf.utils.pipe import pipe
from ietf.utils.test_runner import IetfLiveServerTestCase from ietf.utils.test_runner import IetfLiveServerTestCase
executable_name = 'chromedriver' executable_name = 'geckodriver'
code, out, err = pipe('{} --version'.format(executable_name)) code, out, err = pipe('{} --version'.format(executable_name))
if code != 0: if code != 0:
skip_selenium = True skip_selenium = True
@ -30,20 +30,11 @@ if skip_selenium:
print(" "+skip_message) print(" "+skip_message)
def start_web_driver(): def start_web_driver():
service = Service(executable_path="chromedriver", service = Service(log_output=f"{executable_name}.log", service_args=['--log-no-truncate'])
log_path=settings.TEST_GHOSTDRIVER_LOG_PATH)
service.start()
options = Options() options = Options()
options.add_argument("headless") options.add_argument("--headless")
options.add_argument("disable-extensions") os.environ["MOZ_REMOTE_SETTINGS_DEVTOOLS"] = "1"
options.add_argument("disable-gpu") # headless needs this return webdriver.Firefox(service=service, options=options)
options.add_argument("no-sandbox") # docker needs this
dc = DesiredCapabilities.CHROME
dc["goog:loggingPrefs"] = {"browser": "ALL"}
# For selenium 3:
return webdriver.Chrome("chromedriver", options=options, desired_capabilities=dc)
# For selenium 4:
# return webdriver.Chrome(service=service, options=options, desired_capabilities=dc)
def selenium_enabled(): def selenium_enabled():

View file

@ -65,12 +65,12 @@ types-requests>=2.27.1
requests-mock>=1.9.3 requests-mock>=1.9.3
rfc2html>=2.0.3 rfc2html>=2.0.3
scout-apm>=2.24.2 scout-apm>=2.24.2
selenium>=3.141.0,<4.0 selenium>=4.0
tblib>=1.7.0 # So that the django test runner provides tracebacks tblib>=1.7.0 # So that the django test runner provides tracebacks
tlds>=2022042700 # Used to teach bleach about which TLDs currently exist tlds>=2022042700 # Used to teach bleach about which TLDs currently exist
tqdm>=4.64.0 tqdm>=4.64.0
Unidecode>=1.3.4 Unidecode>=1.3.4
urllib3<2 # v2 causes selenium tests to fail with "Timeout value was <object..." error urllib3>=2
weasyprint>=59 weasyprint>=59
xml2rfc>=3.12.4 xml2rfc>=3.12.4
xym>=0.6,<1.0 xym>=0.6,<1.0