diff --git a/app.json b/app.json index 7b508793..5eabe48e 100644 --- a/app.json +++ b/app.json @@ -29,6 +29,7 @@ "MAGICK_CONFIGURE_PATH": "/app/infra/.magick", "MOVIE_FONT_NAME": "AvantGarde-Book", "IMAGEIO_FFMPEG_EXE": "/app/vendor/ffmpeg/ffmpeg", + "CHROMEDRIVER_PATH": "/app/.chrome-for-testing/chrome-linux64/chrome", "AWS_POLLY_STORAGE_BUCKET": { "description": "AWS bucket name for AWS Polly Speech Synth", @@ -66,8 +67,7 @@ } }, "buildpacks": [ - { "url": "heroku/chromedriver" }, - { "url": "https://github.com/artoonie/heroku-buildpack-google-chrome" }, + { "url": "heroku-community/chrome-for-testing" }, { "url": "heroku/nodejs" }, { "url": "heroku/python" }, { "url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest" }, diff --git a/common/testUtils.py b/common/testUtils.py index 4ca37d52..61bfd4e0 100644 --- a/common/testUtils.py +++ b/common/testUtils.py @@ -4,6 +4,7 @@ import logging import json +import os import tempfile import uuid from urllib.parse import urlparse @@ -13,6 +14,7 @@ from django.contrib.auth.models import Permission from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService from scraper.models import MultiScraper, Scraper from visualizer.models import JsonConfig from visualizer.tests import filenames @@ -66,6 +68,13 @@ def get_headless_browser(cls): """ Returns a headless browser """ chromeOptions = webdriver.chrome.options.Options() chromeOptions.add_argument("--headless") + chromeOptions.add_argument("--no-sandbox") + + if 'CHROMEDRIVER_PATH' in os.environ: + chromeOptions.add_argument("--remote-debugging-port=9222") + service = ChromeService(executable_path=os.environ["CHROMEDRIVER_PATH"]) + return webdriver.Chrome(service=service, options=chromeOptions) + return webdriver.Chrome(options=chromeOptions) # Or, Firefox diff --git a/infra/requirements-core.txt b/infra/requirements-core.txt index 6c4861a3..bf46c004 100644 --- a/infra/requirements-core.txt +++ b/infra/requirements-core.txt @@ -10,7 +10,7 @@ django-sortedm2m==3.1.1 django-storages==1.13.2 Django==4.2.16 rcvformats==0.0.42 -selenium==4.10.0 +selenium==4.25.0 psycopg2-binary==2.9.6 pytz==2023.3 whitenoise==6.4.0 diff --git a/movie/tasks.py b/movie/tasks.py index e91b4d91..89660250 100644 --- a/movie/tasks.py +++ b/movie/tasks.py @@ -9,6 +9,7 @@ from django.conf import settings import requests from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService from movie.creation.movieCreator import MovieCreationFactory from visualizer.models import JsonConfig, MovieGenerationStatuses @@ -54,8 +55,13 @@ def create_movie_task(pk, domain): chromeOptions.add_argument("--headless") chromeOptions.add_argument("--disable-dev-shm-usage") chromeOptions.add_argument("--shm-size=512m") + if 'CHROMEDRIVER_PATH' in os.environ: + chromeOptions.add_argument("--remote-debugging-port=9222") + service = ChromeService(executable_path=os.environ["CHROMEDRIVER_PATH"]) + browser = webdriver.Chrome(service=service, options=chromeOptions) + else: + browser = webdriver.Chrome(options=chromeOptions) - browser = webdriver.Chrome(options=chromeOptions) browser.implicitly_wait(10) try: diff --git a/visualizer/tests/testLiveBrowserHeadless.py b/visualizer/tests/testLiveBrowserHeadless.py index 2ea0b510..df582bdd 100644 --- a/visualizer/tests/testLiveBrowserHeadless.py +++ b/visualizer/tests/testLiveBrowserHeadless.py @@ -18,6 +18,7 @@ from mock import patch from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select from selenium.webdriver.support.ui import WebDriverWait @@ -84,6 +85,8 @@ def _get_eliminated_color(): # Get an eliminated bar by its text bargraph = self.browser.find_element(By.ID, 'bargraph-interactive-body') cssSelector = "path[data-original-title=\"On Round 1, has 64 votes (16%)\"]" + WebDriverWait(self.browser, 5).until( + EC.presence_of_element_located((By.CSS_SELECTOR, cssSelector))) lastBarInLastRoundList = bargraph.find_elements(By.CSS_SELECTOR, cssSelector) self.assertEqual(len(lastBarInLastRoundList), 1) lastBarInLastRound = lastBarInLastRoundList[0] @@ -94,14 +97,22 @@ def _get_eliminated_color(): self._upload(filenames.MULTIWINNER) gray = "rgb(204, 204, 204)" + WebDriverWait(self.browser, 5).until( + EC.visibility_of_element_located((By.ID, "barchart-tab"))) self._ensure_eventually_asserts(lambda: self.assertEqual(_get_eliminated_color(), gray)) # Change option to show a dim version of the last-round color self._go_to_tab("settings-tab") - self.browser.find_elements(By.ID, "bargraphOptions")[0].click() # Open the dropdown + + # Open the dropdown and wait for the animation to complete + self.browser.find_elements(By.ID, "bargraphOptions")[0].click() + + # Select the element and submit options = Select(self.browser.find_element(By.ID, "eliminationBarColor")) options.select_by_index(2) - self.browser.find_elements(By.ID, "updateSettings")[0].click() # Hit submit + WebDriverWait(self.browser, 5).until( + EC.visibility_of_element_located((By.ID, "updateSettings"))) + self.browser.find_elements(By.ID, "updateSettings")[0].submit() notgray = "rgb(238, 237, 241)" self._ensure_eventually_asserts(lambda: self.assertEqual(_get_eliminated_color(), notgray)) @@ -439,6 +450,8 @@ def test_sankey_hide_round_num(self): # Check the box (the second one, which isn't hidden) self.browser.find_elements(By.NAME, "showRoundNumbersOnSankey")[1].click() self.browser.find_element(By.ID, "updateSettings").click() # Hit submit + WebDriverWait(self.browser, 5).until( + EC.visibility_of_element_located((By.ID, "sankey-tab"))) # Go to the bargraph, now it should be zero self._go_to_tab("sankey-tab") @@ -501,7 +514,8 @@ def click_activation_link(): # Try to login before activation: fails, and the username field is still there login_via_upload_redirect() - self.assertEqual(len(self.browser.find_elements(By.ID, "id_username")), 1) + usernameFields = self.browser.find_elements(By.ID, "id_username") + self.assertEqual(len(usernameFields), 1) # Assert an email was sent self.assertEqual(len(test_mailbox.outbox), 1) @@ -512,6 +526,7 @@ def click_activation_link(): # Now login should succeed, and upload has no username field login_via_upload_redirect() + WebDriverWait(self.browser, 5).until(EC.staleness_of(usernameFields[0])) self.assertEqual(len(self.browser.find_elements(By.ID, "id_username")), 0) # And for good measure, upload a file