diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2010bb..58a2013 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,11 +92,36 @@ jobs: - run: python -m pip install .[test] codecov - run: python -m pip install django~=${{ matrix.django-version }}.0 - - run: python -m pytest + - run: python -m pytest -m "not selenium" env: PATH: $PATH:$(pwd)/bin - run: codecov + selenium: + needs: + - pytest + strategy: + matrix: + python-version: + - "3.x" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Chrome + run: sudo apt-get install -y google-chrome-stable + - name: Install Selenium + run: | + mkdir bin + curl -O https://chromedriver.storage.googleapis.com/`curl -s https://chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip + unzip chromedriver_linux64.zip -d bin + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - run: python -m pip install -e .[test] + - run: python -m pytest -m selenium + - uses: codecov/codecov-action@v3 + + analyze: name: CodeQL Analyze needs: diff --git a/tests/conftest.py b/tests/conftest.py index a4d0352..54f7de2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,10 +10,14 @@ from s3file.storages import get_aws_location +def pytest_configure(config): + config.addinivalue_line("markers", "selenium: skip if selenium is not installed") + + @pytest.fixture(scope="session") def driver(): chrome_options = webdriver.ChromeOptions() - chrome_options.headless = True + chrome_options.add_argument("--headless=new") try: b = webdriver.Chrome(options=chrome_options) except WebDriverException as e: diff --git a/tests/test_forms.py b/tests/test_forms.py index 5f45be6..e16ca82 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -135,6 +135,7 @@ def test_accept(self): "application/pdf,image/*" ) + @pytest.mark.selenium def test_no_js_error(self, driver, live_server): driver.get(live_server + self.url) @@ -142,6 +143,7 @@ def test_no_js_error(self, driver, live_server): error = driver.find_element(By.XPATH, "//body[@JSError]") pytest.fail(error.get_attribute("JSError")) + @pytest.mark.selenium def test_file_insert( self, request, driver, live_server, upload_file, freeze_upload_folder ): @@ -157,6 +159,7 @@ def test_file_insert( error = driver.find_element(By.XPATH, "//body[@JSError]") pytest.fail(error.get_attribute("JSError")) + @pytest.mark.selenium def test_file_insert_submit_value( self, driver, live_server, upload_file, freeze_upload_folder ): @@ -179,6 +182,7 @@ def test_file_insert_submit_value( assert "save_continue" in driver.page_source assert "continue_value" in driver.page_source + @pytest.mark.selenium def test_progress(self, driver, live_server, upload_file, freeze_upload_folder): driver.get(live_server + self.url) file_input = driver.find_element(By.XPATH, "//input[@name='file']") @@ -199,6 +203,7 @@ def test_progress(self, driver, live_server, upload_file, freeze_upload_folder): response = json.loads(driver.find_elements(By.CSS_SELECTOR, "pre")[0].text) assert response["POST"]["progress"] == "1" + @pytest.mark.selenium def test_multi_file( self, driver, @@ -208,7 +213,7 @@ def test_multi_file( another_upload_file, yet_another_upload_file, ): - driver.get(live_server + self.url) + driver.get(live_server + reverse("upload-multi")) file_input = driver.find_element(By.XPATH, "//input[@name='file']") file_input.send_keys( " \n ".join( diff --git a/tests/testapp/forms.py b/tests/testapp/forms.py index bd2e195..bd7f2cb 100644 --- a/tests/testapp/forms.py +++ b/tests/testapp/forms.py @@ -10,14 +10,32 @@ ) + forms.ClearableFileInput.__bases__ -class MultipleFileInput(forms.ClearableFileInput): - allow_multiple_selected = True - - class UploadForm(forms.ModelForm): class Meta: model = FileModel fields = ("file", "other_file") - widgets = { - "file": MultipleFileInput(attrs={"multiple": True}), - } + + +class MultipleFileInput(forms.ClearableFileInput): + allow_multiple_selected = True + + +class MultipleFileField(forms.FileField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("widget", MultipleFileInput()) + super().__init__(*args, **kwargs) + + def clean(self, data, initial=None): + single_file_clean = super().clean + if isinstance(data, (list, tuple)): + result = [single_file_clean(d, initial) for d in data] + else: + result = single_file_clean(data, initial) + return result + + +class MultiUploadForm(forms.Form): + file = MultipleFileField( + required=False, widget=MultipleFileInput(attrs={"multiple": True}) + ) + other_file = forms.FileField(required=False) diff --git a/tests/testapp/urls.py b/tests/testapp/urls.py index 8e9a2f2..f9da4df 100644 --- a/tests/testapp/urls.py +++ b/tests/testapp/urls.py @@ -7,4 +7,5 @@ urlpatterns = [ path("", views.ExampleFormView.as_view(), name="upload"), + path("multi/", views.MultiExampleFormView.as_view(), name="upload-multi"), ] diff --git a/tests/testapp/views.py b/tests/testapp/views.py index da3b87e..af2f257 100644 --- a/tests/testapp/views.py +++ b/tests/testapp/views.py @@ -29,3 +29,21 @@ def form_valid(self, form): status=201, encoder=FileEncoder, ) + + +class MultiExampleFormView(generic.FormView): + form_class = forms.MultiUploadForm + template_name = "form.html" + + def form_valid(self, form): + return JsonResponse( + { + "POST": self.request.POST, + "FILES": { + "file": self.request.FILES.getlist("file"), + "other_file": self.request.FILES.getlist("other_file"), + }, + }, + status=201, + encoder=FileEncoder, + )