From b7f07195003a1d0a19e12b854b79edadeaceb4ba Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Wed, 20 Sep 2023 16:34:07 +0200 Subject: [PATCH 01/52] add selenium test in pod main app, add selenium, geckodriver and firefox in docker file, create integration github worklow --- .github/workflows/integration.yml | 72 +++++++++++++++++++ dockerfile-dev-with-volumes/pod/Dockerfile | 11 +++ .../pod/my-entrypoint.sh | 1 + pod/main/integration_tests/__init__.py | 0 pod/main/integration_tests/test_selenium.py | 25 +++++++ pod/main/test_settings.py | 3 + requirements-dev.txt | 1 + 7 files changed, 113 insertions(+) create mode 100644 .github/workflows/integration.yml create mode 100644 pod/main/integration_tests/__init__.py create mode 100644 pod/main/integration_tests/test_selenium.py diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 0000000000..0df24a2e90 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,72 @@ +--- +name: Integration tests + +on: + push: + branches: [master, develop] + pull_request: + branches: ["*"] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: ['3.10'] + + steps: + - uses: actions/checkout@v3 + - uses: browser-actions/setup-geckodriver@latest + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: Configure sysctl limits (for ES) + run: | + sudo swapoff -a + sudo sysctl -w vm.swappiness=1 + sudo sysctl -w fs.file-max=262144 + sudo sysctl -w vm.max_map_count=262144 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + # Remove apt repos that are known to break from time to time + # See https://github.com/actions/virtual-environments/issues/323 + - name: Remove broken apt repos [Ubuntu] + run: | + for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done + + - name: Install Dependencies + run: | + sudo apt-get clean + sudo apt-get update + sudo apt-get install ffmpeg + sudo apt-get install -y ffmpegthumbnailer + sudo apt-get install -y npm + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r requirements-dev.txt + sudo npm install -g yarn + + - name: Runs Elasticsearch + uses: elastic/elastic-github-actions/elasticsearch@refactor_with_plugins + with: + # stack-version: 7.6.0 + stack-version: 6.8.23 + plugins: analysis-icu + + - name: Setup Pod + run: | + coverage run --source='.' manage.py create_pod_index --settings=pod.main.test_settings + python manage.py makemigrations --settings=pod.main.test_settings + python manage.py migrate --settings=pod.main.test_settings + cd pod + yarn + + - name: Run Tests + run: | + export DISPLAY=:99 + geckodriver & + Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional + python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.test_selenium \ No newline at end of file diff --git a/dockerfile-dev-with-volumes/pod/Dockerfile b/dockerfile-dev-with-volumes/pod/Dockerfile index 68f8d08065..8902e37f18 100755 --- a/dockerfile-dev-with-volumes/pod/Dockerfile +++ b/dockerfile-dev-with-volumes/pod/Dockerfile @@ -25,6 +25,17 @@ RUN apt-get clean && apt-get update \ ffmpegthumbnailer \ imagemagick +RUN apt-get install -y firefox-esr xvfb + +# Download, unzip, and install geckodriver +RUN wget https://github.com/mozilla/geckodriver/releases/download/v0.33.0/geckodriver-v0.33.0-linux64.tar.gz +RUN tar -zxf geckodriver-v0.33.0-linux64.tar.gz -C /usr/local/bin +RUN chmod +x /usr/local/bin/geckodriver + +# Set display port and dbus env to avoid hanging +ENV DISPLAY=:99 +ENV DBUS_SESSION_BUS_ADDRESS=/dev/null + WORKDIR /usr/src/app COPY ./requirements.txt . diff --git a/dockerfile-dev-with-volumes/pod/my-entrypoint.sh b/dockerfile-dev-with-volumes/pod/my-entrypoint.sh index 67d58b7229..d912662266 100644 --- a/dockerfile-dev-with-volumes/pod/my-entrypoint.sh +++ b/dockerfile-dev-with-volumes/pod/my-entrypoint.sh @@ -25,5 +25,6 @@ fi # Le serveur de développement permet de tester vos futures modifications facilement. # N'hésitez pas à lancer le serveur de développement pour vérifier vos modifications au fur et à mesure. # À ce niveau, vous devriez avoir le site en français et en anglais et voir l'ensemble de la page d'accueil. +Xvfb -ac :99 -screen 0 1280x1024x24 -nolisten tcp & python3 manage.py runserver 0.0.0.0:8080 --insecure sleep infinity diff --git a/pod/main/integration_tests/__init__.py b/pod/main/integration_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pod/main/integration_tests/test_selenium.py b/pod/main/integration_tests/test_selenium.py new file mode 100644 index 0000000000..3be6614145 --- /dev/null +++ b/pod/main/integration_tests/test_selenium.py @@ -0,0 +1,25 @@ +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium.webdriver.firefox.webdriver import WebDriver + +# python manage.py test -v 3 --settings=pod.main.test_settings pod.main.tests.test_selenium + + +class PodSeleniumTests(StaticLiveServerTestCase): + fixtures = ["initial_data.json"] + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.selenium = WebDriver() + cls.selenium.implicitly_wait(10) + + @classmethod + def tearDownClass(cls): + cls.selenium.quit() + super().tearDownClass() + + def test_home(self): + print(self.live_server_url) + self.selenium.get(f"{self.live_server_url}/") + print(self.selenium.title) + assert "Welcome" in self.selenium.page_source diff --git a/pod/main/test_settings.py b/pod/main/test_settings.py index 8bf1e76897..6a872c80de 100644 --- a/pod/main/test_settings.py +++ b/pod/main/test_settings.py @@ -92,3 +92,6 @@ def get_shared_secret(): XAPI_LRS_URL = "" XAPI_LRS_LOGIN = "" XAPI_LRS_PWD = "" + +# Uniquement lors d'environnement conteneurisé +# MIGRATION_MODULES = {'flatpages': 'pod.db_migrations'} diff --git a/requirements-dev.txt b/requirements-dev.txt index 3c75a548a1..93351c71c1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,3 +5,4 @@ coveralls httmock beautifulsoup4 django-debug-toolbar +selenium From 3ecb4e135da2fa93e24eae3d511e6f0edb2d62d9 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Wed, 20 Sep 2023 17:10:56 +0200 Subject: [PATCH 02/52] rename selenium integration test to not run it with other tests --- .github/workflows/integration.yml | 2 +- .../{test_selenium.py => selenium_pod_integration_tests.py} | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) rename pod/main/integration_tests/{test_selenium.py => selenium_pod_integration_tests.py} (84%) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 0df24a2e90..f90feea1c8 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -69,4 +69,4 @@ jobs: export DISPLAY=:99 geckodriver & Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional - python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.test_selenium \ No newline at end of file + python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests \ No newline at end of file diff --git a/pod/main/integration_tests/test_selenium.py b/pod/main/integration_tests/selenium_pod_integration_tests.py similarity index 84% rename from pod/main/integration_tests/test_selenium.py rename to pod/main/integration_tests/selenium_pod_integration_tests.py index 3be6614145..6f425124c2 100644 --- a/pod/main/integration_tests/test_selenium.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -1,7 +1,8 @@ from django.contrib.staticfiles.testing import StaticLiveServerTestCase from selenium.webdriver.firefox.webdriver import WebDriver -# python manage.py test -v 3 --settings=pod.main.test_settings pod.main.tests.test_selenium +# python manage.py test -v 3 --settings=pod.main.test_settings \ +# pod.main.integration_tests.selenium_pod_integration_tests class PodSeleniumTests(StaticLiveServerTestCase): From 7083b9eada0e9d1a6e880cb83f9f7f87c212da75 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 12:08:30 +0200 Subject: [PATCH 03/52] add integration test in pod workflow action --- .../{integration.yml => integration.back} | 0 .github/workflows/pod.yml | 23 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) rename .github/workflows/{integration.yml => integration.back} (100%) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.back similarity index 100% rename from .github/workflows/integration.yml rename to .github/workflows/integration.back diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index 9b2346ef22..b9c09253b9 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -72,6 +72,20 @@ jobs: PYTHONUNBUFFERED: 1 run: | coverage run --append --source='.' manage.py test -v 3 --settings=pod.main.test_settings + + - name: Add Gecko driver + if: matrix.python-version == '3.9' + uses: browser-actions/setup-geckodriver@latest + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Run Integration Tests + if: matrix.python-version == '3.9' + run: | + export DISPLAY=:99 + geckodriver & + Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional + python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests ## Start Accessibility tests with pa11y ## @@ -110,15 +124,8 @@ jobs: uses: thollander/actions-comment-pull-request@v2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - message: '
Pa11y testing results - - -``` - -${{ steps.pa11y_output.outputs.content }}``` - + message: '
Pa11y testing results```${{ steps.pa11y_output.outputs.content }}```
' -
' - name: Check for pa11y failures. if: contains(steps.pa11y_output.outputs.content, 'Errors in http://') run: | From 676f2cf2522c337abadf34fa8a1eebba0f977a09 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 12:32:14 +0200 Subject: [PATCH 04/52] try : add comment if accessibilty is ok --- .github/workflows/pod.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index b9c09253b9..814022c91f 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -126,6 +126,13 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: '
Pa11y testing results```${{ steps.pa11y_output.outputs.content }}```
' + - name: Comment on pull request. + if: ${{ contains(steps.pa11y_output.outputs.content, 'Errors in http://') == 'false' }} + uses: thollander/actions-comment-pull-request@v2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + message: '
Pa11y testing resultsOK
' + - name: Check for pa11y failures. if: contains(steps.pa11y_output.outputs.content, 'Errors in http://') run: | From 3631e525446a6687b270ef4d0f222327201d1ef9 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 13:49:23 +0200 Subject: [PATCH 05/52] try : echo pa11y --- .github/workflows/pod.yml | 47 ++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index 814022c91f..491a63d723 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.8', '3.9', '3.10'] + python-version: ['3.9'] # ['3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v3 @@ -67,25 +67,25 @@ jobs: cd pod yarn - - name: Run Tests with coverage - env: - PYTHONUNBUFFERED: 1 - run: | - coverage run --append --source='.' manage.py test -v 3 --settings=pod.main.test_settings + # - name: Run Tests with coverage + # env: + # PYTHONUNBUFFERED: 1 + # run: | + # coverage run --append --source='.' manage.py test -v 3 --settings=pod.main.test_settings - - name: Add Gecko driver - if: matrix.python-version == '3.9' - uses: browser-actions/setup-geckodriver@latest - with: - token: ${{ secrets.GITHUB_TOKEN }} + # - name: Add Gecko driver + # if: matrix.python-version == '3.9' + # uses: browser-actions/setup-geckodriver@latest + # with: + # token: ${{ secrets.GITHUB_TOKEN }} - - name: Run Integration Tests - if: matrix.python-version == '3.9' - run: | - export DISPLAY=:99 - geckodriver & - Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional - python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests + # - name: Run Integration Tests + # if: matrix.python-version == '3.9' + # run: | + # export DISPLAY=:99 + # geckodriver & + # Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional + # python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests ## Start Accessibility tests with pa11y ## @@ -127,11 +127,12 @@ jobs: message: '
Pa11y testing results```${{ steps.pa11y_output.outputs.content }}```
' - name: Comment on pull request. - if: ${{ contains(steps.pa11y_output.outputs.content, 'Errors in http://') == 'false' }} - uses: thollander/actions-comment-pull-request@v2 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - message: '
Pa11y testing resultsOK
' + run: echo '${{ steps.pa11y_output.outputs.content }}' + # if: ${{ contains(steps.pa11y_output.outputs.content, 'Errors in http://') == 'false' }} + # uses: thollander/actions-comment-pull-request@v2 + # with: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # message: '
Pa11y testing resultsOK
' - name: Check for pa11y failures. if: contains(steps.pa11y_output.outputs.content, 'Errors in http://') From 05ef9e2218be13d4959badc21e9b42e4404faf17 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 14:07:37 +0200 Subject: [PATCH 06/52] try : pa11y no error --- .github/workflows/pod.yml | 14 ++++++++------ .../selenium_pod_integration_tests.py | 9 ++++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index 491a63d723..b4e072d44f 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -127,12 +127,14 @@ jobs: message: '
Pa11y testing results```${{ steps.pa11y_output.outputs.content }}```
' - name: Comment on pull request. - run: echo '${{ steps.pa11y_output.outputs.content }}' - # if: ${{ contains(steps.pa11y_output.outputs.content, 'Errors in http://') == 'false' }} - # uses: thollander/actions-comment-pull-request@v2 - # with: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # message: '
Pa11y testing resultsOK
' + run: echo '${{ steps.pa11y_output.outputs.content }}' + + - name: Comment Pa11y Ok + if: ${{ !contains(steps.pa11y_output.outputs.content, 'Errors in http://') }} + uses: thollander/actions-comment-pull-request@v2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + message: '
Pa11y testing resultsOK
' - name: Check for pa11y failures. if: contains(steps.pa11y_output.outputs.content, 'Errors in http://') diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 6f425124c2..c6b25ceb56 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -6,21 +6,28 @@ class PodSeleniumTests(StaticLiveServerTestCase): + """Tests the integration of Pod application with Selenium from side files""" fixtures = ["initial_data.json"] @classmethod def setUpClass(cls): + """Create the driver for all selenium tests.""" super().setUpClass() cls.selenium = WebDriver() cls.selenium.implicitly_wait(10) @classmethod def tearDownClass(cls): + """Close the driver used.""" cls.selenium.quit() super().tearDownClass() - def test_home(self): + def Test_selenium_suites(self): + """Run a set of Selenium Test Suites from Side files""" print(self.live_server_url) self.selenium.get(f"{self.live_server_url}/") print(self.selenium.title) assert "Welcome" in self.selenium.page_source + + def run_suite(self, suite_name): + pass From 192fd5656e6fcf249f0b90be49c15c338c26f18f Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 14:27:07 +0200 Subject: [PATCH 07/52] try : add permissions --- .github/workflows/pod.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index b4e072d44f..aa90ecbd5d 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -7,6 +7,8 @@ on: pull_request: branches: ["*"] +permissions: write-all + jobs: build: runs-on: ubuntu-latest From ac6877afa9fcb83bae14f8a79599230597c408e5 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 14:39:59 +0200 Subject: [PATCH 08/52] try : test comment --- .github/workflows/pod.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index aa90ecbd5d..7a4657f466 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -7,7 +7,8 @@ on: pull_request: branches: ["*"] -permissions: write-all +permissions: + pull-requests: write jobs: build: @@ -130,6 +131,12 @@ jobs: - name: Comment on pull request. run: echo '${{ steps.pa11y_output.outputs.content }}' + + - name: Comment PR + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + Hello world ! :wave: - name: Comment Pa11y Ok if: ${{ !contains(steps.pa11y_output.outputs.content, 'Errors in http://') }} From f08759c18c3871501284a13cae204c8a213ea076 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 15:08:38 +0200 Subject: [PATCH 09/52] classic usage of actions comment --- .github/workflows/commentPR.yml | 17 +++++++++++++++++ .github/workflows/{pod.yml => pod.yml.back} | 14 ++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/commentPR.yml rename .github/workflows/{pod.yml => pod.yml.back} (93%) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml new file mode 100644 index 0000000000..981cc568f4 --- /dev/null +++ b/.github/workflows/commentPR.yml @@ -0,0 +1,17 @@ +--- +name: Test Comment PR +on: pull_request + +jobs: + example_comment_pr: + runs-on: ubuntu-latest + name: An example job to comment a PR + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Comment PR + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + Hello world ! :wave: \ No newline at end of file diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml.back similarity index 93% rename from .github/workflows/pod.yml rename to .github/workflows/pod.yml.back index 7a4657f466..02fe0fc914 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml.back @@ -13,6 +13,7 @@ permissions: jobs: build: runs-on: ubuntu-latest + permissions: write-all strategy: max-parallel: 4 matrix: @@ -122,12 +123,12 @@ jobs: with: path: ./pa11y_output.txt - - name: Comment on pull request. - if: contains(steps.pa11y_output.outputs.content, 'Errors in http://') - uses: thollander/actions-comment-pull-request@v2 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - message: '
Pa11y testing results```${{ steps.pa11y_output.outputs.content }}```
' + # - name: Comment on pull request. + # if: contains(steps.pa11y_output.outputs.content, 'Errors in http://') + # uses: thollander/actions-comment-pull-request@v2 + # with: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # message: '
Pa11y testing results```${{ steps.pa11y_output.outputs.content }}```
' - name: Comment on pull request. run: echo '${{ steps.pa11y_output.outputs.content }}' @@ -135,6 +136,7 @@ jobs: - name: Comment PR uses: thollander/actions-comment-pull-request@v2 with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | Hello world ! :wave: From cdd5afe94c3d43ed8b11e376a8f972db6a3873ee Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 15:09:53 +0200 Subject: [PATCH 10/52] classic usage of actions comment - add permissions --- .github/workflows/commentPR.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index 981cc568f4..a8773402ba 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -5,6 +5,10 @@ on: pull_request jobs: example_comment_pr: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + repository-projects: write name: An example job to comment a PR steps: - name: Checkout From d9d94079e836a921b42fe7c911a1fbe345924077 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 15:12:27 +0200 Subject: [PATCH 11/52] classic usage of actions comment - add GITHUB TOKEN --- .github/workflows/commentPR.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index a8773402ba..38a0f32fc6 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -17,5 +17,6 @@ jobs: - name: Comment PR uses: thollander/actions-comment-pull-request@v2 with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} message: | Hello world ! :wave: \ No newline at end of file From 635cf3b67f49a55323ca7c59c46e6b0e7f5d3f93 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 15:26:11 +0200 Subject: [PATCH 12/52] add permissions and test issues --- .github/workflows/commentPR.yml | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index 38a0f32fc6..a8d3011926 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -4,11 +4,13 @@ on: pull_request jobs: example_comment_pr: + permissions: write-all runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - repository-projects: write + # permissions: + # contents: write + # pull-requests: write + # repository-projects: write + # issues: write name: An example job to comment a PR steps: - name: Checkout @@ -17,6 +19,17 @@ jobs: - name: Comment PR uses: thollander/actions-comment-pull-request@v2 with: + message: 'Hello world ! :wave:' GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - message: | - Hello world ! :wave: \ No newline at end of file + + - name: Create issue using REST API + run: | + curl --request POST \ + --url https://api.github.com/repos/${{ github.repository }}/issues \ + --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ + --header 'content-type: application/json' \ + --data '{ + "title": "Automated issue for commit: ${{ github.sha }}", + "body": "This issue was automatically created by the GitHub Action workflow **${{ github.workflow }}**. \n\n The commit hash was: _${{ github.sha }}_." + }' \ + --fail \ No newline at end of file From 1872b38e235dd7c9812b414e78bf317aa61bbfb6 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 15:35:08 +0200 Subject: [PATCH 13/52] change order --- .github/workflows/commentPR.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index a8d3011926..a366f33943 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -16,12 +16,6 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 - with: - message: 'Hello world ! :wave:' - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Create issue using REST API run: | curl --request POST \ @@ -32,4 +26,10 @@ jobs: "title": "Automated issue for commit: ${{ github.sha }}", "body": "This issue was automatically created by the GitHub Action workflow **${{ github.workflow }}**. \n\n The commit hash was: _${{ github.sha }}_." }' \ - --fail \ No newline at end of file + --fail + + - name: Comment PR + uses: thollander/actions-comment-pull-request@v2 + with: + message: 'Hello world ! :wave:' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 4ad22b37db673477453348d4d0519b2b7729759e Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 15:49:18 +0200 Subject: [PATCH 14/52] try curl --- .github/workflows/commentPR.yml | 39 ++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index a366f33943..3f5816d834 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -16,20 +16,29 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Create issue using REST API + # - uses: actions/github-script@v6 + # with: + # script: | + # github.rest.issues.createComment({ + # issue_number: context.issue.number, + # owner: context.repo.owner, + # repo: context.repo.repo, + # body: '👋 Thanks for reporting!' + # }) + - name: Add comment to PR + env: + URL: ${{ github.event.pull_request.comments_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - curl --request POST \ - --url https://api.github.com/repos/${{ github.repository }}/issues \ - --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'content-type: application/json' \ - --data '{ - "title": "Automated issue for commit: ${{ github.sha }}", - "body": "This issue was automatically created by the GitHub Action workflow **${{ github.workflow }}**. \n\n The commit hash was: _${{ github.sha }}_." - }' \ - --fail + curl \ + -X POST \ + $URL \ + -H "Content-Type: application/json" \ + -H "Authorization: token $GITHUB_TOKEN" \ + --data '{ "body": "blah blah" }' - - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 - with: - message: 'Hello world ! :wave:' - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # - name: Comment PR + # uses: thollander/actions-comment-pull-request@v2 + # with: + # message: 'Hello world ! :wave:' + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 6a3c128590d3afbef5a568b2b8d81ca963473f01 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 15:53:36 +0200 Subject: [PATCH 15/52] try script --- .github/workflows/commentPR.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index 3f5816d834..f67dd30172 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -4,7 +4,7 @@ on: pull_request jobs: example_comment_pr: - permissions: write-all + permissions: read-all runs-on: ubuntu-latest # permissions: # contents: write @@ -16,15 +16,16 @@ jobs: - name: Checkout uses: actions/checkout@v3 - # - uses: actions/github-script@v6 - # with: - # script: | - # github.rest.issues.createComment({ - # issue_number: context.issue.number, - # owner: context.repo.owner, - # repo: context.repo.repo, - # body: '👋 Thanks for reporting!' - # }) + - uses: actions/github-script@v6 + with: + script: | + github.rest.pull_request.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '👋 Thanks for reporting!' + }) + - name: Add comment to PR env: URL: ${{ github.event.pull_request.comments_url }} From b8a650618e63abc9e854a30e8434859463a794da Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:00:37 +0200 Subject: [PATCH 16/52] try script with issue --- .github/workflows/commentPR.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index f67dd30172..bb263b08e4 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -4,7 +4,7 @@ on: pull_request jobs: example_comment_pr: - permissions: read-all + permissions: write-all runs-on: ubuntu-latest # permissions: # contents: write @@ -19,13 +19,12 @@ jobs: - uses: actions/github-script@v6 with: script: | - github.rest.pull_request.createComment({ + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: '👋 Thanks for reporting!' }) - - name: Add comment to PR env: URL: ${{ github.event.pull_request.comments_url }} From 1f8928016c51e65470e4f377f5d504419e74d398 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:02:10 +0200 Subject: [PATCH 17/52] try script with issue and read permissions --- .github/workflows/commentPR.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index bb263b08e4..e2ece4a7fb 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -4,7 +4,7 @@ on: pull_request jobs: example_comment_pr: - permissions: write-all + permissions: read-all runs-on: ubuntu-latest # permissions: # contents: write From 822516e60bca1115a466f4fefdf2138b67920583 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:06:46 +0200 Subject: [PATCH 18/52] try another use curl --- .github/workflows/commentPR.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index e2ece4a7fb..3b5a1a858a 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -16,18 +16,18 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - uses: actions/github-script@v6 - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: '👋 Thanks for reporting!' - }) + # - uses: actions/github-script@v6 + # with: + # script: | + # github.rest.issues.createComment({ + # issue_number: context.issue.number, + # owner: context.repo.owner, + # repo: context.repo.repo, + # body: '👋 Thanks for reporting!' + # }) - name: Add comment to PR env: - URL: ${{ github.event.pull_request.comments_url }} + URL: ${{ github.event.issues.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | curl \ From 101f4dee68c29e7bb4580c70ec164ed670500630 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:08:41 +0200 Subject: [PATCH 19/52] try another use curl 2 --- .github/workflows/commentPR.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index 3b5a1a858a..ef918f9b84 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -27,9 +27,10 @@ jobs: # }) - name: Add comment to PR env: - URL: ${{ github.event.issues.comments_url }} + URL: ${{ github.event.issue.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + echo '${{ github.event.issue.comments_url }}' curl \ -X POST \ $URL \ From 2576eb54ae7e1ce2bd87e1d93562bca4cecaed74 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:28:15 +0200 Subject: [PATCH 20/52] pull request target --- .github/workflows/commentPR.yml | 47 ++++++++++----------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index ef918f9b84..cfc416e626 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -1,45 +1,26 @@ --- name: Test Comment PR -on: pull_request +on: + push: + branches: [master, develop] + pull_request_target: + branches: [master, develop] jobs: example_comment_pr: - permissions: read-all runs-on: ubuntu-latest - # permissions: - # contents: write - # pull-requests: write - # repository-projects: write - # issues: write + permissions: + contents: write + pull-requests: write + repository-projects: write + issues: write name: An example job to comment a PR steps: - name: Checkout uses: actions/checkout@v3 - # - uses: actions/github-script@v6 - # with: - # script: | - # github.rest.issues.createComment({ - # issue_number: context.issue.number, - # owner: context.repo.owner, - # repo: context.repo.repo, - # body: '👋 Thanks for reporting!' - # }) - - name: Add comment to PR - env: - URL: ${{ github.event.issue.comments_url }} + - name: Comment PR + uses: thollander/actions-comment-pull-request@v2 + with: + message: 'Hello world ! :wave:' GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - echo '${{ github.event.issue.comments_url }}' - curl \ - -X POST \ - $URL \ - -H "Content-Type: application/json" \ - -H "Authorization: token $GITHUB_TOKEN" \ - --data '{ "body": "blah blah" }' - - # - name: Comment PR - # uses: thollander/actions-comment-pull-request@v2 - # with: - # message: 'Hello world ! :wave:' - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b794c66ca9f6cc76c1d1711d9858b5ab17ba033d Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:29:35 +0200 Subject: [PATCH 21/52] pull request target 2 --- .github/workflows/commentPR.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index cfc416e626..99221b36a9 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -4,7 +4,7 @@ on: push: branches: [master, develop] pull_request_target: - branches: [master, develop] + branches: ["*"] jobs: example_comment_pr: From 13edd02fabcf68bbc5307f43b041b6e149899e68 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:33:32 +0200 Subject: [PATCH 22/52] pull request target add type and branche --- .github/workflows/commentPR.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index 99221b36a9..24560e7bec 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -4,7 +4,8 @@ on: push: branches: [master, develop] pull_request_target: - branches: ["*"] + types: [assigned, opened, synchronize, reopened] + branches: [master, develop] jobs: example_comment_pr: From 32d88c2a9048a4c01f894c827c7c5aa9e252256f Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:38:18 +0200 Subject: [PATCH 23/52] pull request target remove type --- .github/workflows/commentPR.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index 24560e7bec..bde9310182 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -1,10 +1,7 @@ --- name: Test Comment PR on: - push: - branches: [master, develop] pull_request_target: - types: [assigned, opened, synchronize, reopened] branches: [master, develop] jobs: From 46556442e4afaf04d76f3b6403cb90789fec6f96 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:48:40 +0200 Subject: [PATCH 24/52] pull request target star --- .github/workflows/commentPR.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index bde9310182..6228d98e39 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -2,7 +2,8 @@ name: Test Comment PR on: pull_request_target: - branches: [master, develop] + branches: + - '*' jobs: example_comment_pr: From 780a19a7702da04d182ede0693427de65fcb9d23 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:52:33 +0200 Subject: [PATCH 25/52] pull request target another try --- .github/workflows/commentPR.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index 6228d98e39..3e44275083 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -1,18 +1,13 @@ --- name: Test Comment PR on: - pull_request_target: - branches: - - '*' + pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using + types: [opened, edited] jobs: example_comment_pr: runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - repository-projects: write - issues: write + permissions: write-all name: An example job to comment a PR steps: - name: Checkout From bcbd751e4700cf2b768484b90d7892571e83c17b Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 16:59:29 +0200 Subject: [PATCH 26/52] test pull-request-target-branch-action --- .github/workflows/commentPR.yml | 50 ++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index 3e44275083..9932a1b653 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -1,20 +1,42 @@ ---- -name: Test Comment PR +name: Make sure new PRs are sent to development + on: - pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using - types: [opened, edited] + pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using + types: [opened, edited] jobs: - example_comment_pr: + check-branch: runs-on: ubuntu-latest - permissions: write-all - name: An example job to comment a PR steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 - with: - message: 'Hello world ! :wave:' + - uses: Vankka/pr-target-branch-action@v2 + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + target: main + exclude: development # Don't prevent going from development -> main + change-to: development + comment: | + Your PR was set to target `main`, PRs should be target `development` + The base branch of this PR has been automatically changed to `development`, please check that there are no merge conflicts + + +# --- +# name: Test Comment PR +# on: +# pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using +# types: [opened, edited] + +# jobs: +# example_comment_pr: +# runs-on: ubuntu-latest +# permissions: write-all +# name: An example job to comment a PR +# steps: +# - name: Checkout +# uses: actions/checkout@v3 + +# - name: Comment PR +# uses: thollander/actions-comment-pull-request@v2 +# with: +# message: 'Hello world ! :wave:' +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 4c07fd69298fd6e74fec9faca6557b70bf1f6231 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 17:07:13 +0200 Subject: [PATCH 27/52] test all types --- .github/workflows/commentPR.yml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index 9932a1b653..b1ff5ba207 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -1,8 +1,27 @@ name: Make sure new PRs are sent to development on: + push: + branches: [master, develop] pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using - types: [opened, edited] + types: + - assigned + - unassigned + - labeled + - unlabeled + - opened + - edited + - closed + - reopened + - synchronize + - converted_to_draft + - ready_for_review + - locked + - unlocked + - review_requested + - review_request_removed + - auto_merge_enabled + - auto_merge_disabled jobs: check-branch: From 36f00d71f6c7e414e27ff26815453d46b0ec7458 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 21 Sep 2023 17:12:51 +0200 Subject: [PATCH 28/52] remove pull_request_targat --- .github/workflows/commentPR.yml | 88 +++++++++++++-------------------- 1 file changed, 33 insertions(+), 55 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index b1ff5ba207..bbc47132ce 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -1,61 +1,39 @@ -name: Make sure new PRs are sent to development - +--- +name: Test Comment PR on: - push: - branches: [master, develop] - pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using - types: - - assigned - - unassigned - - labeled - - unlabeled - - opened - - edited - - closed - - reopened - - synchronize - - converted_to_draft - - ready_for_review - - locked - - unlocked - - review_requested - - review_request_removed - - auto_merge_enabled - - auto_merge_disabled + push: + branches: [master, develop] + # pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using + # types: + # - assigned + # - unassigned + # - labeled + # - unlabeled + # - opened + # - edited + # - closed + # - reopened + # - synchronize + # - converted_to_draft + # - ready_for_review + # - locked + # - unlocked + # - review_requested + # - review_request_removed + # - auto_merge_enabled + # - auto_merge_disabled jobs: - check-branch: + example_comment_pr: runs-on: ubuntu-latest + permissions: write-all + name: An example job to comment a PR steps: - - uses: Vankka/pr-target-branch-action@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - target: main - exclude: development # Don't prevent going from development -> main - change-to: development - comment: | - Your PR was set to target `main`, PRs should be target `development` - The base branch of this PR has been automatically changed to `development`, please check that there are no merge conflicts - + - name: Checkout + uses: actions/checkout@v3 -# --- -# name: Test Comment PR -# on: -# pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using -# types: [opened, edited] - -# jobs: -# example_comment_pr: -# runs-on: ubuntu-latest -# permissions: write-all -# name: An example job to comment a PR -# steps: -# - name: Checkout -# uses: actions/checkout@v3 - -# - name: Comment PR -# uses: thollander/actions-comment-pull-request@v2 -# with: -# message: 'Hello world ! :wave:' -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Comment PR + uses: thollander/actions-comment-pull-request@v2 + with: + message: 'Hello world ! :wave:' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From ee39dd386b6e8be805fef23accee6366455576ba Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Fri, 22 Sep 2023 14:43:05 +0200 Subject: [PATCH 29/52] little test --- .github/workflows/commentPR.yml | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml index bbc47132ce..90ae52c892 100644 --- a/.github/workflows/commentPR.yml +++ b/.github/workflows/commentPR.yml @@ -3,25 +3,25 @@ name: Test Comment PR on: push: branches: [master, develop] - # pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using - # types: - # - assigned - # - unassigned - # - labeled - # - unlabeled - # - opened - # - edited - # - closed - # - reopened - # - synchronize - # - converted_to_draft - # - ready_for_review - # - locked - # - unlocked - # - review_requested - # - review_request_removed - # - auto_merge_enabled - # - auto_merge_disabled + pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using + types: + - assigned + - unassigned + - labeled + - unlabeled + - opened + - edited + - closed + - reopened + - synchronize + - converted_to_draft + - ready_for_review + - locked + - unlocked + - review_requested + - review_request_removed + - auto_merge_enabled + - auto_merge_disabled jobs: example_comment_pr: From 7feb00079876231d41b0ff0efdf3e31e1bf352d5 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Fri, 22 Sep 2023 15:06:35 +0200 Subject: [PATCH 30/52] add integration test in pod workflow - remove add comment --- .github/workflows/commentPR.yml | 39 ----------- .github/workflows/integration.back | 72 --------------------- .github/workflows/{pod.yml.back => pod.yml} | 58 +++++------------ 3 files changed, 17 insertions(+), 152 deletions(-) delete mode 100644 .github/workflows/commentPR.yml delete mode 100644 .github/workflows/integration.back rename .github/workflows/{pod.yml.back => pod.yml} (69%) diff --git a/.github/workflows/commentPR.yml b/.github/workflows/commentPR.yml deleted file mode 100644 index 90ae52c892..0000000000 --- a/.github/workflows/commentPR.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Test Comment PR -on: - push: - branches: [master, develop] - pull_request_target: # Please read https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ before using - types: - - assigned - - unassigned - - labeled - - unlabeled - - opened - - edited - - closed - - reopened - - synchronize - - converted_to_draft - - ready_for_review - - locked - - unlocked - - review_requested - - review_request_removed - - auto_merge_enabled - - auto_merge_disabled - -jobs: - example_comment_pr: - runs-on: ubuntu-latest - permissions: write-all - name: An example job to comment a PR - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 - with: - message: 'Hello world ! :wave:' - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/integration.back b/.github/workflows/integration.back deleted file mode 100644 index f90feea1c8..0000000000 --- a/.github/workflows/integration.back +++ /dev/null @@ -1,72 +0,0 @@ ---- -name: Integration tests - -on: - push: - branches: [master, develop] - pull_request: - branches: ["*"] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - max-parallel: 4 - matrix: - python-version: ['3.10'] - - steps: - - uses: actions/checkout@v3 - - uses: browser-actions/setup-geckodriver@latest - with: - token: ${{ secrets.GITHUB_TOKEN }} - - name: Configure sysctl limits (for ES) - run: | - sudo swapoff -a - sudo sysctl -w vm.swappiness=1 - sudo sysctl -w fs.file-max=262144 - sudo sysctl -w vm.max_map_count=262144 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - # Remove apt repos that are known to break from time to time - # See https://github.com/actions/virtual-environments/issues/323 - - name: Remove broken apt repos [Ubuntu] - run: | - for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done - - - name: Install Dependencies - run: | - sudo apt-get clean - sudo apt-get update - sudo apt-get install ffmpeg - sudo apt-get install -y ffmpegthumbnailer - sudo apt-get install -y npm - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install -r requirements-dev.txt - sudo npm install -g yarn - - - name: Runs Elasticsearch - uses: elastic/elastic-github-actions/elasticsearch@refactor_with_plugins - with: - # stack-version: 7.6.0 - stack-version: 6.8.23 - plugins: analysis-icu - - - name: Setup Pod - run: | - coverage run --source='.' manage.py create_pod_index --settings=pod.main.test_settings - python manage.py makemigrations --settings=pod.main.test_settings - python manage.py migrate --settings=pod.main.test_settings - cd pod - yarn - - - name: Run Tests - run: | - export DISPLAY=:99 - geckodriver & - Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional - python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests \ No newline at end of file diff --git a/.github/workflows/pod.yml.back b/.github/workflows/pod.yml similarity index 69% rename from .github/workflows/pod.yml.back rename to .github/workflows/pod.yml index 02fe0fc914..6a5a62b123 100644 --- a/.github/workflows/pod.yml.back +++ b/.github/workflows/pod.yml @@ -71,25 +71,25 @@ jobs: cd pod yarn - # - name: Run Tests with coverage - # env: - # PYTHONUNBUFFERED: 1 - # run: | - # coverage run --append --source='.' manage.py test -v 3 --settings=pod.main.test_settings + - name: Run Tests with coverage + env: + PYTHONUNBUFFERED: 1 + run: | + coverage run --append --source='.' manage.py test -v 3 --settings=pod.main.test_settings - # - name: Add Gecko driver - # if: matrix.python-version == '3.9' - # uses: browser-actions/setup-geckodriver@latest - # with: - # token: ${{ secrets.GITHUB_TOKEN }} + - name: Add Gecko driver + if: matrix.python-version == '3.9' + uses: browser-actions/setup-geckodriver@latest + with: + token: ${{ secrets.GITHUB_TOKEN }} - # - name: Run Integration Tests - # if: matrix.python-version == '3.9' - # run: | - # export DISPLAY=:99 - # geckodriver & - # Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional - # python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests + - name: Run Integration Tests + if: matrix.python-version == '3.9' + run: | + export DISPLAY=:99 + geckodriver & + Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional + python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests ## Start Accessibility tests with pa11y ## @@ -123,30 +123,6 @@ jobs: with: path: ./pa11y_output.txt - # - name: Comment on pull request. - # if: contains(steps.pa11y_output.outputs.content, 'Errors in http://') - # uses: thollander/actions-comment-pull-request@v2 - # with: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # message: '
Pa11y testing results```${{ steps.pa11y_output.outputs.content }}```
' - - - name: Comment on pull request. - run: echo '${{ steps.pa11y_output.outputs.content }}' - - - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - message: | - Hello world ! :wave: - - - name: Comment Pa11y Ok - if: ${{ !contains(steps.pa11y_output.outputs.content, 'Errors in http://') }} - uses: thollander/actions-comment-pull-request@v2 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - message: '
Pa11y testing resultsOK
' - - name: Check for pa11y failures. if: contains(steps.pa11y_output.outputs.content, 'Errors in http://') run: | From 1750786369a7a93f786fc026ff2a810ad364b962 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Fri, 22 Sep 2023 15:09:10 +0200 Subject: [PATCH 31/52] add python 3.8 and 3.10 in matrix test --- .github/workflows/pod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index 6a5a62b123..f37c98ef40 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -17,7 +17,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.9'] # ['3.8', '3.9', '3.10'] + python-version: ['3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v3 From ed79b40be8dba7f09a99bc7bd9fa3b6b4ce3ebb0 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Mon, 25 Sep 2023 11:19:56 +0200 Subject: [PATCH 32/52] add directory to store side selenium. rename test selenium suite and work on parse side directory --- .../selenium_pod_integration_tests.py | 17 ++- .../sides/pod-configuration.side | 131 ++++++++++++++++++ 2 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 pod/main/integration_tests/sides/pod-configuration.side diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index c6b25ceb56..61d07784b1 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -1,11 +1,14 @@ from django.contrib.staticfiles.testing import StaticLiveServerTestCase from selenium.webdriver.firefox.webdriver import WebDriver +import glob +import json + # python manage.py test -v 3 --settings=pod.main.test_settings \ # pod.main.integration_tests.selenium_pod_integration_tests -class PodSeleniumTests(StaticLiveServerTestCase): +class PodSeleniumTest(StaticLiveServerTestCase): """Tests the integration of Pod application with Selenium from side files""" fixtures = ["initial_data.json"] @@ -22,12 +25,22 @@ def tearDownClass(cls): cls.selenium.quit() super().tearDownClass() - def Test_selenium_suites(self): + def test_selenium_suites(self): """Run a set of Selenium Test Suites from Side files""" + print("\n\n - test_selenium_suites - ") + sides = glob.glob(r"sides/*.side") + print(sides) + for side in sides: + print(side) + with open('./sides/%s' % side) as json_file: + data = json.load(json_file) + print(data) + print(self.live_server_url) self.selenium.get(f"{self.live_server_url}/") print(self.selenium.title) assert "Welcome" in self.selenium.page_source + print("\n\n - test_selenium_suites - ") def run_suite(self, suite_name): pass diff --git a/pod/main/integration_tests/sides/pod-configuration.side b/pod/main/integration_tests/sides/pod-configuration.side new file mode 100644 index 0000000000..b895cacc2d --- /dev/null +++ b/pod/main/integration_tests/sides/pod-configuration.side @@ -0,0 +1,131 @@ +{ + "id": "96bdfd57-c9ad-4b38-b5f6-c32a68957e33", + "version": "2.0", + "name": "pod3.4.0", + "url": "http://localhost:9090", + "tests": [{ + "id": "f27edc9b-9e82-4944-a554-acdf4008d084", + "name": "switch_to_dark_mode", + "commands": [{ + "id": "75113f62-909d-4cfb-bd66-654892f501b2", + "comment": "", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "395352bc-9f40-4ce8-9706-6e1160a73340", + "comment": "", + "command": "click", + "target": "id=okcookie", + "targets": [ + ["id=okcookie", "id"], + ["css=#okcookie", "css:finder"], + ["xpath=//button[@id='okcookie']", "xpath:attributes"], + ["xpath=//div[@id='cookieModal']/div/div/div/div/button", "xpath:idRelative"], + ["xpath=//div[3]/div/div/div/div/button", "xpath:position"], + ["xpath=//button[contains(.,'J’ai compris')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "5824164e-d42a-46c5-9608-850b3a2804e1", + "comment": "", + "command": "click", + "target": "id=pod-param-buttons__button", + "targets": [ + ["id=pod-param-buttons__button", "id"], + ["css=#pod-param-buttons__button", "css:finder"], + ["xpath=//button[@id='pod-param-buttons__button']", "xpath:attributes"], + ["xpath=//li[@id='pod-param-buttons']/button", "xpath:idRelative"], + ["xpath=//nav/div/ul/li/button", "xpath:position"] + ], + "value": "" + }, { + "id": "9f3a14ec-b937-4bd5-98d1-9d650dfc1b77", + "comment": "", + "command": "click", + "target": "css=.theme-switch > .slider", + "targets": [ + ["css=.theme-switch > .slider", "css:finder"], + ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li[2]/label/span[2]/span", "xpath:idRelative"], + ["xpath=//span[2]/span", "xpath:position"] + ], + "value": "" + }, { + "id": "eaa1d134-f372-4a8b-84d0-c3bb0689c03f", + "comment": "", + "command": "click", + "target": "css=#pod-navbar__menusettings > .offcanvas-body", + "targets": [ + ["css=#pod-navbar__menusettings > .offcanvas-body", "css:finder"], + ["xpath=//div[@id='pod-navbar__menusettings']/div[2]", "xpath:idRelative"], + ["xpath=//li/div/div[2]", "xpath:position"] + ], + "value": "" + }] + }, { + "id": "70df9a16-53b3-4a29-845f-f8264ec89cb3", + "name": "switch_to_dyslexia_mode", + "commands": [{ + "id": "a5d16f0f-5b79-4e39-baa2-9d5d26935229", + "comment": "", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "a3b14c5e-9c01-4c6b-aa1f-fb56669b83cd", + "comment": "", + "command": "setWindowSize", + "target": "1303x889", + "targets": [], + "value": "" + }, { + "id": "a472f852-2116-4c9b-b1f0-13417fd0ceb3", + "comment": "", + "command": "click", + "target": "id=pod-param-buttons__button", + "targets": [ + ["id=pod-param-buttons__button", "id"], + ["css=#pod-param-buttons__button", "css:finder"], + ["xpath=//button[@id='pod-param-buttons__button']", "xpath:attributes"], + ["xpath=//li[@id='pod-param-buttons']/button", "xpath:idRelative"], + ["xpath=//nav/div/ul/li/button", "xpath:position"] + ], + "value": "" + }, { + "id": "9be70e4b-70c6-4ae2-b9b2-0fd4b8d3e664", + "comment": "", + "command": "click", + "target": "css=.dyslexia-switch > .slider", + "targets": [ + ["css=.dyslexia-switch > .slider", "css:finder"], + ["xpath=//li[@id='dyslexia-switch-wrapper']/label/span[2]/span", "xpath:idRelative"], + ["xpath=//li[3]/label/span[2]/span", "xpath:position"] + ], + "value": "" + }, { + "id": "63742661-2b25-4525-9f40-55b5e2343160", + "comment": "", + "command": "click", + "target": "css=#pod-navbar__menusettings .btn-close", + "targets": [ + ["css=#pod-navbar__menusettings .btn-close", "css:finder"], + ["xpath=(//button[@type='button'])[4]", "xpath:attributes"], + ["xpath=//div[@id='pod-navbar__menusettings']/div/button", "xpath:idRelative"], + ["xpath=//li/div/div/button", "xpath:position"] + ], + "value": "" + }] + }], + "suites": [{ + "id": "b6a92037-b365-4211-adea-2b6725c3ed63", + "name": "Default Suite", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["f27edc9b-9e82-4944-a554-acdf4008d084"] + }], + "urls": ["http://localhost:9090/"], + "plugins": [] +} \ No newline at end of file From 5e475557eb23771ddcbdc9c89d7cd0ae7acf1504 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Wed, 4 Oct 2023 16:08:28 +0200 Subject: [PATCH 33/52] Implement selenium integration tests --- .github/workflows/pod.yml | 8 +- pod/main/integration_tests/cookies.side | 35 ++ pod/main/integration_tests/lang.side | 92 +++++ pod/main/integration_tests/search.side | 82 +++++ .../selenium_pod_integration_tests.py | 335 +++++++++++++++++- pod/video/integration_tests/comment.side | 146 ++++++++ requirements-dev.txt | 1 + 7 files changed, 687 insertions(+), 12 deletions(-) create mode 100644 pod/main/integration_tests/cookies.side create mode 100644 pod/main/integration_tests/lang.side create mode 100644 pod/main/integration_tests/search.side create mode 100644 pod/video/integration_tests/comment.side diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index 6a5a62b123..120228ddf6 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -7,8 +7,8 @@ on: pull_request: branches: ["*"] -permissions: - pull-requests: write +permissions: + pull-requests: write jobs: build: @@ -76,13 +76,13 @@ jobs: PYTHONUNBUFFERED: 1 run: | coverage run --append --source='.' manage.py test -v 3 --settings=pod.main.test_settings - + - name: Add Gecko driver if: matrix.python-version == '3.9' uses: browser-actions/setup-geckodriver@latest with: token: ${{ secrets.GITHUB_TOKEN }} - + - name: Run Integration Tests if: matrix.python-version == '3.9' run: | diff --git a/pod/main/integration_tests/cookies.side b/pod/main/integration_tests/cookies.side new file mode 100644 index 0000000000..b44c38231e --- /dev/null +++ b/pod/main/integration_tests/cookies.side @@ -0,0 +1,35 @@ +{ + "id": "f060a750-1757-4b5d-8ed0-ce6b1c49b62b", + "version": "2.0", + "name": "pod-front", + "url": "http://localhost:9090/", + "tests": [{ + "id": "pod-accept-cookies", + "name": "cookies", + "commands": [{ + "id": "pod-open-home", + "comment": "Ouvre la page d'accueil", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "pod-accept-cookies", + "comment": "Valide les cookies du navigateur", + "command": "click", + "target": "id=okcookie", + "targets": [], + "value": "" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-accept-cookies"] + }], + "urls": ["http://localhost:9090/"], + "plugins": [] +} diff --git a/pod/main/integration_tests/lang.side b/pod/main/integration_tests/lang.side new file mode 100644 index 0000000000..2321b5bb5f --- /dev/null +++ b/pod/main/integration_tests/lang.side @@ -0,0 +1,92 @@ +{ + "id": "f060a750-1757-4b5d-8ed0-ce6b1c49b62b", + "version": "2.0", + "name": "pod-front", + "url": "http://localhost:9090/", + "tests": [{ + "id": "pod-change-lang", + "name": "lang", + "commands": [{ + "id": "pod-open-home", + "comment": "Ouvre la page d'accueil", + "command": "open", + "target": "/", + "targets": [], + "value": "" + },{ + "id": "pod-open-configuration", + "comment": "Ouvre la page de configuration", + "command": "click", + "target": "id=pod-param-buttons__button", + "targets": [ + ["id=pod-param-buttons__button", "id"], + ["css=#pod-param-buttons__button", "css:finder"], + ["xpath=//button[@id='pod-param-buttons__button']", "xpath:attributes"], + ["xpath=//li[@id='pod-param-buttons']/button", "xpath:idRelative"], + ["xpath=//nav/div/ul/li/button", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-toggle-langue-menu", + "comment": "Sélectionne menu choix langue", + "command": "mouseOver", + "target": "id=pod-lang-select", + "targets": [ + ["id=pod-lang-select", "id"], + ["css=#pod-lang-select", "css:finder"], + ["xpath=//button[@id='pod-lang-select']", "xpath:attributes"], + ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/button", "xpath:idRelative"], + ["xpath=//div[2]/ul/li/div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-toggle-lang-select", + "comment": "Sélectionne la langue", + "command": "click", + "target": "id=pod-lang-select", + "targets": [ + ["id=pod-lang-select", "id"], + ["css=#pod-lang-select", "css:finder"], + ["xpath=//button[@id='pod-lang-select']", "xpath:attributes"], + ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/button", "xpath:idRelative"], + ["xpath=//div[2]/ul/li/div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-toggle-lang-deselect", + "comment": "Déselectionne langue actuelle", + "command": "mouseOut", + "target": "id=pod-lang-select", + "targets": [ + ["id=pod-lang-select", "id"], + ["css=#pod-lang-select", "css:finder"], + ["xpath=//button[@id='pod-lang-select']", "xpath:attributes"], + ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/button", "xpath:idRelative"], + ["xpath=//div[2]/ul/li/div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-toggle-lang-confirmation", + "comment": "Sélectionne langue ciblée", + "command": "click", + "target": "css=.dropdown-item", + "targets": [ + ["css=.dropdown-item", "css:finder"], + ["xpath=//input[@value='English (en)']", "xpath:attributes"], + ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/div/form/input[3]", "xpath:idRelative"], + ["xpath=//input[3]", "xpath:position"] + ], + "value": "" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-change-lang"] + }], + "urls": ["http://localhost:9090/"], + "plugins": [] +} diff --git a/pod/main/integration_tests/search.side b/pod/main/integration_tests/search.side new file mode 100644 index 0000000000..9a5b2b1e09 --- /dev/null +++ b/pod/main/integration_tests/search.side @@ -0,0 +1,82 @@ +{ + "id": "f060a750-1757-4b5d-8ed0-ce6b1c49b62b", + "version": "2.0", + "name": "pod-front", + "url": "http://localhost:9090/", + "tests": [{ + "id": "pod-searchbar", + "name": "search", + "commands": [{ + "id": "pod-open-home", + "comment": "Ouvre la page d'accueil", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "pod-search", + "comment": "Sélectionne barre recherche", + "command": "click", + "target": "id=s", + "targets": [ + ["id=s", "id"], + ["name=q", "name"], + ["css=#s", "css:finder"], + ["xpath=//input[@id='s']", "xpath:attributes"], + ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-search-type", + "comment": "Tape mot clé", + "command": "type", + "target": "id=s", + "targets": [ + ["id=s", "id"], + ["name=q", "name"], + ["css=#s", "css:finder"], + ["xpath=//input[@id='s']", "xpath:attributes"], + ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "pas de résultat attendu" + }, { + "id": "pod-search-enter", + "comment": "Appuie touche entrée", + "command": "sendKeys", + "target": "id=s", + "targets": [ + ["id=s", "id"], + ["name=q", "name"], + ["css=#s", "css:finder"], + ["xpath=//input[@id='s']", "xpath:attributes"], + ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "pod-accueil-ariane", + "comment": "Retour à la page d'accueil via logo pod", + "command": "click", + "target": "xpath=//div[@id='nav-mainbar']/a/strong", + "targets": [ + ["css=strong", "css:finder"], + ["xpath=//div[@id='nav-mainbar']/a/strong", "xpath:idRelative"], + ["xpath=//strong", "xpath:position"], + ["xpath=//strong[contains(.,'Lille.Pod')]", "xpath:innerText"] + ], + "value": "" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-searchbar"] + }], + "urls": ["http://localhost:9090/"], + "plugins": [] +} diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index c6b25ceb56..c63879c37f 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -1,12 +1,288 @@ from django.contrib.staticfiles.testing import StaticLiveServerTestCase from selenium.webdriver.firefox.webdriver import WebDriver +from selenium.webdriver.common.by import By +from selenium.common.exceptions import NoSuchElementException +import json +import os +from pyvirtualdisplay import Display +from django.conf import settings +from selenium.webdriver.support.ui import Select +from django.test import override_settings # python manage.py test -v 3 --settings=pod.main.test_settings \ # pod.main.integration_tests.selenium_pod_integration_tests +target_cache = {} + + +def find_element(driver, target): + """find an element in the page""" + + if target in target_cache: + target = target_cache[target] + + if target.startswith('link='): + try: + return driver.find_element(By.LINK_TEXT, target[5:]) + except NoSuchElementException: + result = driver.find_element(By.LINK_TEXT, target[5:].lower()) + target_cache[target] = 'link=' + target[5:].lower() + msg = ' label %s is being cached as %s' + print(msg, target, target_cache[target]) + return result + elif target.startswith('//'): + return driver.find_element(By.XPATH, target) + + elif target.startswith('xpath='): + return driver.find_element(By.XPATH, target[6:]) + + elif target.startswith('css='): + return driver.find_element(By.CSS_SELECTOR, target[4:]) + + elif target.startswith('id='): + return driver.find_element(By.ID, target[3:]) + + elif target.startswith('name='): + return driver.find_element(By.NAME, target[5:]) + else: + direct = ( + driver.find_element(By.NAME, target) + or driver.find_element(By.ID, target) + or driver.find_element(By.LINK_TEXT, target) + ) + if direct: + return direct + raise Exception('Don\'t know how to find %s' % target) + + +class SeleniumTestCase(object): + """A Single Selenium Test Case""" + + def __init__(self, test_data, callback=None): + self.test_data = test_data + self.callback = callback + + def run(self, driver, url): + print(f'Running test: {self.test_data["name"]}') + self.run_commands(driver, url) + if self.callback: + self.callback(driver.page_source) + + def run_commands(self, driver, url): + base_url = self.test_data.get('url', url) + for command in self.test_data.get('commands', []): + command_name = command.get('command', '') + target = command.get('target', '') + value = command.get('value', '') + + if command_name == 'open': + driver.get(base_url + target) + elif command_name == 'click': + self.click(driver, target) + + def open(self, driver, target): + driver.get(self.base_url + target) + + def click(self, driver, target): + element = find_element(driver, target) + driver.execute_script("arguments[0].scrollIntoView();", element) + element.click() + + def clickAndWait(self, driver, target): + self.click(driver, target) + + def type(self, driver, target, text=''): + element = find_element(driver, target) + element.click() + element.clear() + element.send_keys(text) + + def select(self, driver, target, value): + element = find_element(driver, target) + if value.startswith('label='): + Select(element).select_by_visible_text(value[6:]) + else: + msg = "Don\'t know how to select %s on %s" + raise Exception(msg % (value, target)) + + def verifyTextPresent(self, driver, text): + try: + source = driver.page_source + assert bool(text in source) + except: + print( + 'verifyTextPresent: ', + repr(text), + 'not present in', + repr(source) + ) + raise + + def verifyTextNotPresent(self, driver, text): + try: + assert not bool(text in driver.page_source) + except: + print( + 'verifyNotTextPresent: ', + repr(text), + 'present' + ) + raise + + def assertElementPresent(self, driver, target): + try: + assert bool(find_element(driver, target)) + except: + print('assertElementPresent: ', repr(target), 'not present') + raise + + def verifyElementPresent(self, driver, target): + try: + assert bool(find_element(driver, target)) + except: + print('verifyElementPresent: ', repr(target), 'not present') + raise + + def verifyElementNotPresent(self, driver, target): + present = True + try: + find_element(driver, target) + except NoSuchElementException: + present = False + + try: + assert not present + except: + print('verifyElementNotPresent: ', repr(target), 'present') + raise + + def waitForTextPresent(self, driver, text): + try: + assert bool(text in driver.page_source) + except: + print('waitForTextPresent: ', repr(text), 'not present') + raise + + def waitForTextNotPresent(self, driver, text): + try: + assert not bool(text in driver.page_source) + except: + print('waitForTextNotPresent: ', repr(text), 'present') + raise + + def assertText(self, driver, target, value=u''): + try: + target_value = find_element(driver, target).text + print(' assertText target value =' + repr(target_value)) + if value.startswith('exact:'): + assert target_value == value[len('exact:'):] + else: + assert target_value == value + except: + print( + 'assertText: ', + repr(target), + repr(find_element(driver, target).get_attribute('value')), + repr(value), + ) + raise + + def assertNotText(self, driver, target, value=u''): + try: + target_value = find_element(driver, target).text + print(' assertNotText target value =' + repr(target_value)) + if value.startswith('exact:'): + assert target_value != value[len('exact:'):] + else: + assert target_value != value + except: + print( + 'assertNotText: ', + repr(target), + repr(find_element(driver, target).get_attribute('value')), + repr(value), + ) + raise + + def assertValue(self, driver, target, value=u''): + try: + target_value = find_element(driver, target).get_attribute('value') + print(' assertValue target value =' + repr(target_value)) + assert target_value == value + except: + print( + 'assertValue: ', + repr(target), + repr(find_element(driver, target).get_attribute('value')), + repr(value), + ) + raise + + def assertNotValue(self, driver, target, value=u''): + try: + target_value = find_element(driver, target).get_attribute('value') + print(' assertNotValue target value =' + repr(target_value)) + assert target_value != value + except: + print( + 'assertNotValue: ', + repr(target), + repr(target_value), + repr(value), + ) + raise + + def verifyValue(self, driver, target, value=u''): + try: + target_value = find_element(driver, target).get_attribute('value') + print(' verifyValue target value =' + repr(target_value)) + assert target_value == value + except: + print( + 'verifyValue: ', + repr(target), + repr(find_element(driver, target).get_attribute('value')), + repr(value), + ) + raise + + def selectWindow(self, driver, window): + pass + + +class SeleniumTestSuite(object): + """A Selenium Test Suite""" + + def __init__(self, filename, callback=None): + self.filename = filename + self.callback = callback + + self.tests = [] + + with open(filename, 'r') as json_file: + json_data = json.load(json_file) + self.title = json_data.get('name', 'Untitled Suite') + self.tests = [(test_data.get('name', 'Untitled Test'), test_data) + for test_data in json_data.get('tests', [])] + + def run(self, driver, url): + print("ICI AVEC : ", driver, url) + for test_title, test_data in self.tests: + try: + test = SeleniumTestCase(test_data, self.callback) + test.run(driver, url) + except Exception as e: + print(f'Error in {test_title} ({test.filename}): {e}') + raise + + def __repr__(self): + test_titles = '\n'.join([title for title, _ in self.tests]) + return f'{self.title}\n{test_titles}' + class PodSeleniumTests(StaticLiveServerTestCase): """Tests the integration of Pod application with Selenium from side files""" + fixtures = ["initial_data.json"] @classmethod @@ -22,12 +298,55 @@ def tearDownClass(cls): cls.selenium.quit() super().tearDownClass() - def Test_selenium_suites(self): - """Run a set of Selenium Test Suites from Side files""" - print(self.live_server_url) - self.selenium.get(f"{self.live_server_url}/") - print(self.selenium.title) - assert "Welcome" in self.selenium.page_source + @override_settings(DEBUG=False) + def test_selenium_suites(self): + """Run Selenium Test Suites from Side files in all installed apps""" + run_all_tests() - def run_suite(self, suite_name): - pass + +def run_suite(suite_name, suite_url): + print(f'Running test suite: {suite_name}') + + with open(suite_name, 'r') as json_file: + suite_data = json.load(json_file) + suite_tests = suite_data.get('tests', []) + + for test_data in suite_tests: + test_case = SeleniumTestCase(test_data) + try: + test_case.run(PodSeleniumTests.selenium, suite_url) + except Exception as e: + PodSeleniumTests.selenium.save_screenshot(f'{suite_name}-error_screen.png') + print(f'Error in suite {suite_name}: {e}') + + +def run_all_tests(): + for app in settings.INSTALLED_APPS: + app_module = __import__(app, fromlist=["integration_tests"]) + integration_tests_dir = os.path.join( + os.path.dirname(app_module.__file__), "integration_tests" + ) + if os.path.exists(integration_tests_dir): + print(f"Running Selenium tests in {app}...") + run_tests_in_directory(integration_tests_dir) + + +def run_tests_in_directory(directory): + for root, dirs, files in os.walk(directory): + for filename in files: + if filename.endswith('.side'): + suite_name = os.path.join(root, filename) + run_single_suite(suite_name) + + +def run_single_suite(suite_name): + with Display(visible=0, size=(1024, 768)): + with WebDriver() as driver: + PodSeleniumTests.selenium = driver + PodSeleniumTests.setUpClass() + try: + suite_url = "http://localhost:8080" + run_suite(suite_name, suite_url) + except Exception as e: + print(f'Error in suite {suite_name}: {e}') + PodSeleniumTests.tearDownClass() diff --git a/pod/video/integration_tests/comment.side b/pod/video/integration_tests/comment.side new file mode 100644 index 0000000000..dce561ba7c --- /dev/null +++ b/pod/video/integration_tests/comment.side @@ -0,0 +1,146 @@ +{ + "id": "8c4be61c-31b6-47d7-b3ca-e3e9912e0014", + "version": "2.0", + "name": "pod-front", + "url": "http://localhost:9090/", + "tests": [{ + "id": "pod-add-delete-comment", + "name": "comment", + "commands": [{ + "id": "pod-open-video-page", + "comment": "Ouvre la page de la vidéo", + "command": "open", + "target": "video/5811-les-oiseaux-video-exemple/", + "targets": [], + "value": "" + }, { + "id": "pod-target-comment", + "comment": "Cible la zone de commentaire", + "command": "click", + "target": "id=comment", + "targets": [ + ["id=comment", "id"], + ["name=new_parent_comment", "name"], + ["css=#comment", "css:finder"], + ["xpath=//textarea[@id='comment']", "xpath:attributes"], + ["xpath=//div[@id='pod_comment_wrapper']/div/div/form/div/textarea", "xpath:idRelative"], + ["xpath=//form/div/textarea", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-write-comment", + "comment": "écrit dans la zone de commentaire", + "command": "type", + "target": "id=comment", + "targets": [ + ["id=comment", "id"], + ["name=new_parent_comment", "name"], + ["css=#comment", "css:finder"], + ["xpath=//textarea[@id='comment']", "xpath:attributes"], + ["xpath=//div[@id='pod_comment_wrapper']/div/div/form/div/textarea", "xpath:idRelative"], + ["xpath=//form/div/textarea", "xpath:position"] + ], + "value": "Bonjour !" + }, { + "id": "pod-send-comment", + "comment": "clique sur le bouton \"commenter !\"", + "command": "click", + "target": "css=.add_comment_btn", + "targets": [ + ["css=.add_comment_btn", "css:finder"], + ["xpath=(//button[@type='submit'])[2]", "xpath:attributes"], + ["xpath=//div[@id='pod_comment_wrapper']/div/div/form/div[2]/button", "xpath:idRelative"], + ["xpath=//form/div[2]/button", "xpath:position"], + ["xpath=//button[contains(.,'Commenter !')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "pod-favorite-comment", + "comment": "Met en favori le commentaire", + "command": "click", + "target": "css=.unvoted > .bi", + "targets": [ + ["css=.unvoted > .bi", "css:finder"], + ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div/div/div/span/i", "xpath:idRelative"], + ["xpath=//div/span/i", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-select-answer-zone", + "comment": "Sélectionne la zone de réponse", + "command": "click", + "target": "css=.comment_response_btn", + "targets": [ + ["css=.comment_response_btn", "css:finder"], + ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div/div[2]/span", "xpath:idRelative"], + ["xpath=//div[3]/div/div[2]/span", "xpath:position"], + ["xpath=//span[contains(.,'Répondre')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "pod-write-answer", + "comment": "écrit une réponse", + "command": "type", + "target": "name=new_comment", + "targets": [ + ["name=new_comment", "name"], + ["css=.add_comment > #comment_1695815656609", "css:finder"], + ["xpath=//textarea[@id='comment_1695815656609']", "xpath:attributes"], + ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div[2]/div/textarea", "xpath:idRelative"], + ["xpath=//div[2]/div/textarea", "xpath:position"] + ], + "value": "réponse" + }, { + "id": "pod-send-answer", + "comment": "envoi la réponse", + "command": "click", + "target": "css=.bi-send-fill", + "targets": [ + ["css=.bi-send-fill", "css:finder"], + ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div[2]/div/button/i", "xpath:idRelative"], + ["xpath=//div[3]/div[2]/div/button/i", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-scroll-up", + "comment": "remonte la page (à garder ?)", + "command": "runScript", + "target": "window.scrollTo(0,720)", + "targets": [], + "value": "" + }, { + "id": "pod-select-delete-comment", + "comment": "Clique sur supprimer", + "command": "click", + "target": "css=.comment_actions:nth-child(4) .bi", + "targets": [ + ["css=.comment_actions:nth-child(4) .bi", "css:finder"], + ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div/div[4]/div/i", "xpath:idRelative"], + ["xpath=//div[4]/div/i", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-delete-comment", + "comment": "Valide la suppression", + "command": "click", + "target": "css=.delete", + "targets": [ + ["css=.delete", "css:finder"], + ["xpath=//confirm-modal[@id='custom_element_confirm_modal']/div/div/div/div[3]/button", "xpath:idRelative"], + ["xpath=//confirm-modal/div/div/div/div[3]/button", "xpath:position"], + ["xpath=//button[contains(.,'Supprimer')]", "xpath:innerText"] + ], + "value": "" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-add-delete-comment"] + }], + "urls": ["http://localhost:9090/", "https://pod-test.univ-lille.fr/"], + "plugins": [] +} diff --git a/requirements-dev.txt b/requirements-dev.txt index 93351c71c1..a410922b1c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,3 +6,4 @@ httmock beautifulsoup4 django-debug-toolbar selenium +pyvirtualdisplay From 826f14b1a7f8213c9bd091bbad65cffec5aa0867 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Thu, 5 Oct 2023 10:25:21 +0200 Subject: [PATCH 34/52] Add USE_DEBUG_TOOLBAR parameter --- pod/main/configuration.json | 21 ++++++++++++++++++++- pod/main/settings.py | 7 +++++++ pod/main/test_settings.py | 1 + pod/settings.py | 2 +- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/pod/main/configuration.json b/pod/main/configuration.json index 562e6b1481..fffbb32846 100644 --- a/pod/main/configuration.json +++ b/pod/main/configuration.json @@ -4021,7 +4021,9 @@ "default_value": true, "description": { "en": [ - "" + "A boolean value to enable or disable debug mode.
", + "Never deploy a production site with DEBUG enabled.
", + "__ref: [https://docs.djangoproject.com/fr/3.2/ref/settings/#debug]()__" ], "fr": [ "Une valeur booléenne qui active ou désactive le mode de débogage.
", @@ -4032,6 +4034,23 @@ "pod_version_end": "", "pod_version_init": "3.1" }, + "USE_DEBUG_TOOLBAR": { + "default_value": true, + "description": { + "en": [ + "A boolean value that enables or disables the debugging tool.
", + "Never deploy a production site with USE_DEBUG_TOOLBAR enabled.
", + "__ref: [https://django-debug-toolbar.readthedocs.io/en/latest/]()__" + ], + "fr": [ + "Une valeur booléenne qui active ou désactive l'outil de débogage.
", + "Ne déployez jamais de site en production avec le réglage USE_DEBUG_TOOLBAR activé.
", + "__ref: [https://django-debug-toolbar.readthedocs.io/en/latest/]()__" + ] + }, + "pod_version_end": "", + "pod_version_init": "3.5.0" + }, "LOGIN_URL": { "default_value": "/authentication_login/", "description": { diff --git a/pod/main/settings.py b/pod/main/settings.py index b69eb76206..a03b765e9d 100644 --- a/pod/main/settings.py +++ b/pod/main/settings.py @@ -36,6 +36,13 @@ # SECURITY WARNING: MUST be set to False when deploying into production. DEBUG = True +## +# DEBUG_TOOLBAR option activation +# +# https://django-debug-toolbar.readthedocs.io/en/latest/ +# +USE_DEBUG_TOOLBAR = True + ## # A list of strings representing the host/domain names # that this Django site is allowed to serve. diff --git a/pod/main/test_settings.py b/pod/main/test_settings.py index 6a872c80de..71b870fb1a 100644 --- a/pod/main/test_settings.py +++ b/pod/main/test_settings.py @@ -70,6 +70,7 @@ USE_MEETING = True +USE_DEBUG_TOOLBAR = False def get_shared_secret(): api_mate_url = "https://bigbluebutton.org/api-mate/" diff --git a/pod/settings.py b/pod/settings.py index 00a1179592..47c81e1cdb 100644 --- a/pod/settings.py +++ b/pod/settings.py @@ -459,7 +459,7 @@ def update_settings(local_settings): for variable in the_update_settings: locals()[variable] = the_update_settings[variable] -if locals()["DEBUG"] is True and importlib.util.find_spec("debug_toolbar") is not None: +if locals()["DEBUG"] is True and importlib.util.find_spec("debug_toolbar") is not None and locals()["USE_DEBUG_TOOLBAR"]: INSTALLED_APPS.append("debug_toolbar") MIDDLEWARE = [ "debug_toolbar.middleware.DebugToolbarMiddleware", From aadaca61ad483adf706a8cc34bcbadb82d842da0 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Thu, 5 Oct 2023 10:38:35 +0200 Subject: [PATCH 35/52] Add automatic compilation when merging --- .github/workflows/code_formatting.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index a1ee4ea2d3..80591d1270 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -65,3 +65,17 @@ jobs: run: | git commit -am "Auto-update configuration files" git push + + # Compile lang files + - name: Compile lang files in fr and nl + run: python manage.py compilemessages -l fr -l nl + + - name: Check for modified files + id: compilation-git-check + run: echo modified=$(if git diff --quiet; then echo "false"; else echo "true"; fi) >> $GITHUB_OUTPUT + + - name: Push lang compilation changes + if: steps.compilation-git-check.outputs.modified == 'true' + run: | + git commit -am "Compile lang files" + git push From 5eb38611f83293822af319f3d11b2546965b0dfe Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Fri, 6 Oct 2023 10:14:50 +0200 Subject: [PATCH 36/52] Add custom commands --- .../commands/cookies_commands.side | 12 ++++++++++++ .../selenium_pod_integration_tests.py | 16 +++++++++++++--- .../integration_tests/{ => tests}/cookies.side | 0 pod/main/integration_tests/{ => tests}/lang.side | 6 +++++- .../integration_tests/{ => tests}/search.side | 4 ++++ .../integration_tests/{ => tests}/comment.side | 4 ++++ 6 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 pod/main/integration_tests/commands/cookies_commands.side rename pod/main/integration_tests/{ => tests}/cookies.side (100%) rename pod/main/integration_tests/{ => tests}/lang.side (95%) rename pod/main/integration_tests/{ => tests}/search.side (94%) rename pod/video/integration_tests/{ => tests}/comment.side (97%) diff --git a/pod/main/integration_tests/commands/cookies_commands.side b/pod/main/integration_tests/commands/cookies_commands.side new file mode 100644 index 0000000000..0e01c47f71 --- /dev/null +++ b/pod/main/integration_tests/commands/cookies_commands.side @@ -0,0 +1,12 @@ +{ + "commands": [ + { + "id": "pod-accept-cookies", + "comment": "Valide les cookies du navigateur", + "command": "click", + "target": "id=okcookie", + "targets": [], + "value": "" + } + ] +} diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index c63879c37f..7b2cc8bf6a 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -79,6 +79,14 @@ def run_commands(self, driver, url): driver.get(base_url + target) elif command_name == 'click': self.click(driver, target) + elif command_name == "cookies_commands": + self.cookies_commands(driver, url) + + def cookies_commands(self, driver, url): + with open('pod/main/integration_tests/commands/cookies_commands.side', 'r') as side_file: + side_data = json.load(side_file) + self.test_data['commands'] = side_data.get('commands', []) + self.run_commands(driver, url) def open(self, driver, target): driver.get(self.base_url + target) @@ -327,8 +335,10 @@ def run_all_tests(): os.path.dirname(app_module.__file__), "integration_tests" ) if os.path.exists(integration_tests_dir): - print(f"Running Selenium tests in {app}...") - run_tests_in_directory(integration_tests_dir) + tests_dir = os.path.join(integration_tests_dir, "tests") + if os.path.exists(tests_dir): + print(f"Running Selenium tests in {app}...") + run_tests_in_directory(tests_dir) def run_tests_in_directory(directory): @@ -340,7 +350,7 @@ def run_tests_in_directory(directory): def run_single_suite(suite_name): - with Display(visible=0, size=(1024, 768)): + with Display(visible=0, size=(1920, 1080)): with WebDriver() as driver: PodSeleniumTests.selenium = driver PodSeleniumTests.setUpClass() diff --git a/pod/main/integration_tests/cookies.side b/pod/main/integration_tests/tests/cookies.side similarity index 100% rename from pod/main/integration_tests/cookies.side rename to pod/main/integration_tests/tests/cookies.side diff --git a/pod/main/integration_tests/lang.side b/pod/main/integration_tests/tests/lang.side similarity index 95% rename from pod/main/integration_tests/lang.side rename to pod/main/integration_tests/tests/lang.side index 2321b5bb5f..dd181e9976 100644 --- a/pod/main/integration_tests/lang.side +++ b/pod/main/integration_tests/tests/lang.side @@ -13,7 +13,11 @@ "target": "/", "targets": [], "value": "" - },{ + }, { + "id": "pod-accept-cookies", + "comment": "Valide les cookies du navigateur", + "command": "cookies_commands" + }, { "id": "pod-open-configuration", "comment": "Ouvre la page de configuration", "command": "click", diff --git a/pod/main/integration_tests/search.side b/pod/main/integration_tests/tests/search.side similarity index 94% rename from pod/main/integration_tests/search.side rename to pod/main/integration_tests/tests/search.side index 9a5b2b1e09..c8313566d7 100644 --- a/pod/main/integration_tests/search.side +++ b/pod/main/integration_tests/tests/search.side @@ -13,6 +13,10 @@ "target": "/", "targets": [], "value": "" + }, { + "id": "pod-accept-cookies", + "comment": "Valide les cookies du navigateur", + "command": "cookies_commands" }, { "id": "pod-search", "comment": "Sélectionne barre recherche", diff --git a/pod/video/integration_tests/comment.side b/pod/video/integration_tests/tests/comment.side similarity index 97% rename from pod/video/integration_tests/comment.side rename to pod/video/integration_tests/tests/comment.side index dce561ba7c..adcae7a9a7 100644 --- a/pod/video/integration_tests/comment.side +++ b/pod/video/integration_tests/tests/comment.side @@ -7,6 +7,10 @@ "id": "pod-add-delete-comment", "name": "comment", "commands": [{ + "id": "pod-accept-cookies", + "comment": "Valide les cookies du navigateur", + "command": "cookies_commands" + }, { "id": "pod-open-video-page", "comment": "Ouvre la page de la vidéo", "command": "open", From 0dc7a7c2a369d41242fb893ef03db0c36228a766 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Fri, 6 Oct 2023 10:37:22 +0200 Subject: [PATCH 37/52] Simplify function run() --- .../selenium_pod_integration_tests.py | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 7b2cc8bf6a..cc172607ab 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -61,32 +61,30 @@ class SeleniumTestCase(object): def __init__(self, test_data, callback=None): self.test_data = test_data self.callback = callback + self.base_url = None + self.commands = [] def run(self, driver, url): + self.base_url = url print(f'Running test: {self.test_data["name"]}') - self.run_commands(driver, url) + for command in self.commands: + method_name, target, value = command + method = getattr(self, method_name) + args = [driver] + if target is not None: + args.append(target) + if value is not None: + args.append(value) + print(f' {method_name} {args}') + method(*args) if self.callback: self.callback(driver.page_source) - def run_commands(self, driver, url): - base_url = self.test_data.get('url', url) - for command in self.test_data.get('commands', []): - command_name = command.get('command', '') - target = command.get('target', '') - value = command.get('value', '') - - if command_name == 'open': - driver.get(base_url + target) - elif command_name == 'click': - self.click(driver, target) - elif command_name == "cookies_commands": - self.cookies_commands(driver, url) - def cookies_commands(self, driver, url): with open('pod/main/integration_tests/commands/cookies_commands.side', 'r') as side_file: side_data = json.load(side_file) self.test_data['commands'] = side_data.get('commands', []) - self.run_commands(driver, url) + self.run(driver, url) def open(self, driver, target): driver.get(self.base_url + target) From d1104241d1d460b5a7e225add277c330334bd8f1 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Fri, 6 Oct 2023 11:44:59 +0200 Subject: [PATCH 38/52] Add initial datas --- .../selenium_pod_integration_tests.py | 18 ++++++++++++++++++ pod/main/test_settings.py | 1 + pod/video/integration_tests/tests/comment.side | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index cc172607ab..8ec522af10 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -8,6 +8,8 @@ from django.conf import settings from selenium.webdriver.support.ui import Select from django.test import override_settings +from django.contrib.auth.models import User +from pod.video.models import Video, Type # python manage.py test -v 3 --settings=pod.main.test_settings \ # pod.main.integration_tests.selenium_pod_integration_tests @@ -291,6 +293,10 @@ class PodSeleniumTests(StaticLiveServerTestCase): fixtures = ["initial_data.json"] + def setUp(self): + """Set up the tests.""" + self.initialize_data() + @classmethod def setUpClass(cls): """Create the driver for all selenium tests.""" @@ -309,6 +315,17 @@ def test_selenium_suites(self): """Run Selenium Test Suites from Side files in all installed apps""" run_all_tests() + def initialize_data(self): + """Initialize custom test data.""" + self.user = User.objects.create_user(username="testuser", password="testpassword") + self.video = Video.objects.create( + title="first-video", + owner=self.user, + video="video.mp4", + is_draft=False, + type=Type.objects.get(id=1), + ) + def run_suite(suite_name, suite_url): print(f'Running test suite: {suite_name}') @@ -337,6 +354,7 @@ def run_all_tests(): if os.path.exists(tests_dir): print(f"Running Selenium tests in {app}...") run_tests_in_directory(tests_dir) + print("FIN de TOUS les TESTS") def run_tests_in_directory(directory): diff --git a/pod/main/test_settings.py b/pod/main/test_settings.py index 71b870fb1a..ba89d61c0e 100644 --- a/pod/main/test_settings.py +++ b/pod/main/test_settings.py @@ -72,6 +72,7 @@ USE_DEBUG_TOOLBAR = False + def get_shared_secret(): api_mate_url = "https://bigbluebutton.org/api-mate/" response = requests.get(api_mate_url) diff --git a/pod/video/integration_tests/tests/comment.side b/pod/video/integration_tests/tests/comment.side index adcae7a9a7..557ac4f6d2 100644 --- a/pod/video/integration_tests/tests/comment.side +++ b/pod/video/integration_tests/tests/comment.side @@ -14,7 +14,7 @@ "id": "pod-open-video-page", "comment": "Ouvre la page de la vidéo", "command": "open", - "target": "video/5811-les-oiseaux-video-exemple/", + "target": "video/1-first-video/", "targets": [], "value": "" }, { From 23389a55dce473a82a4078a0642168646a79fbfa Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Fri, 6 Oct 2023 14:09:53 +0200 Subject: [PATCH 39/52] Add documentation --- .../selenium_pod_integration_tests.py | 337 +++++++++++++++--- pod/main/integration_tests/tests/cookies.side | 5 +- 2 files changed, 285 insertions(+), 57 deletions(-) diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 8ec522af10..0fa4eec367 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -18,8 +18,21 @@ def find_element(driver, target): - """find an element in the page""" + """ + Find and return a web element using various locator strategies (e.g., By.ID, By.XPATH, By.CSS_SELECTOR). + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The target element identifier, which can start with 'link=', '//', 'xpath=', 'css=', 'id=', or 'name='. + + Returns: + WebElement: The located web element. + + Raises: + NoSuchElementException: If the specified element is not found on the web page. + Exception: If the target identifier format is not recognized. + + """ if target in target_cache: target = target_cache[target] @@ -61,12 +74,28 @@ class SeleniumTestCase(object): """A Single Selenium Test Case""" def __init__(self, test_data, callback=None): + """ + Initialize a SeleniumTestCase instance. + + Args: + test_data (dict): Test case data, including name, URL, and commands. + callback (callable, optional): Callback function to be executed after the test. + + """ self.test_data = test_data self.callback = callback self.base_url = None self.commands = [] def run(self, driver, url): + """ + Run the Selenium test case. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + url (str): The base URL for the test case. + + """ self.base_url = url print(f'Running test: {self.test_data["name"]}') for command in self.commands: @@ -83,29 +112,78 @@ def run(self, driver, url): self.callback(driver.page_source) def cookies_commands(self, driver, url): + """ + Execute custom commands to handle cookies acceptance on website. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + url (str): The base URL for the test case. + + """ with open('pod/main/integration_tests/commands/cookies_commands.side', 'r') as side_file: side_data = json.load(side_file) self.test_data['commands'] = side_data.get('commands', []) self.run(driver, url) def open(self, driver, target): + """ + Open a URL in the WebDriver. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The URL to open. + + """ driver.get(self.base_url + target) def click(self, driver, target): + """ + Click on a web element identified by a target. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be clicked. + + """ element = find_element(driver, target) driver.execute_script("arguments[0].scrollIntoView();", element) element.click() def clickAndWait(self, driver, target): + """ + Click on a web element identified by a target and wait for a response. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be clicked. + + """ self.click(driver, target) def type(self, driver, target, text=''): + """ + Simulate typing text into an input field on a web page. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the input field. + text (str): The text to be typed into the input field (optional). + """ element = find_element(driver, target) element.click() element.clear() element.send_keys(text) def select(self, driver, target, value): + """ + Select an option from a dropdown list on a web page. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the dropdown element. + value (str): The option value to be selected. + + """ element = find_element(driver, target) if value.startswith('label='): Select(element).select_by_visible_text(value[6:]) @@ -114,6 +192,14 @@ def select(self, driver, target, value): raise Exception(msg % (value, target)) def verifyTextPresent(self, driver, text): + """ + Verify if the specified text is present in the page source. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + text (str): The text to be verified for presence. + + """ try: source = driver.page_source assert bool(text in source) @@ -127,6 +213,14 @@ def verifyTextPresent(self, driver, text): raise def verifyTextNotPresent(self, driver, text): + """ + Verify if the specified text is not present in the page source. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + text (str): The text to be verified for absence. + + """ try: assert not bool(text in driver.page_source) except: @@ -138,6 +232,14 @@ def verifyTextNotPresent(self, driver, text): raise def assertElementPresent(self, driver, target): + """ + Assert the presence of a specified web element. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be verified for presence. + + """ try: assert bool(find_element(driver, target)) except: @@ -145,6 +247,14 @@ def assertElementPresent(self, driver, target): raise def verifyElementPresent(self, driver, target): + """ + Verify the presence of a specified web element on a web page. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be verified for presence. + + """ try: assert bool(find_element(driver, target)) except: @@ -152,6 +262,15 @@ def verifyElementPresent(self, driver, target): raise def verifyElementNotPresent(self, driver, target): + """ + Verify the absence of a specified web element on a web page. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be verified for absence. + + """ + present = True try: find_element(driver, target) @@ -165,6 +284,15 @@ def verifyElementNotPresent(self, driver, target): raise def waitForTextPresent(self, driver, text): + """ + Wait for the specified text to be present in the page source. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + text (str): The text to wait for in the page source. + + """ + try: assert bool(text in driver.page_source) except: @@ -172,6 +300,15 @@ def waitForTextPresent(self, driver, text): raise def waitForTextNotPresent(self, driver, text): + """ + Wait for the specified text to be absent in the page source. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + text (str): The text to wait for to be absent in the page source. + + """ + try: assert not bool(text in driver.page_source) except: @@ -179,6 +316,16 @@ def waitForTextNotPresent(self, driver, text): raise def assertText(self, driver, target, value=u''): + """ + Assert that the text of a specified web element matches the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose text needs to be asserted. + value (str, optional): The expected text value to compare against (default is an empty string). + + """ + try: target_value = find_element(driver, target).text print(' assertText target value =' + repr(target_value)) @@ -196,6 +343,16 @@ def assertText(self, driver, target, value=u''): raise def assertNotText(self, driver, target, value=u''): + """ + Assert that the text of a specified web element does not match the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose text needs to be asserted. + value (str, optional): The expected text value to compare against (default is an empty string). + + """ + try: target_value = find_element(driver, target).text print(' assertNotText target value =' + repr(target_value)) @@ -213,6 +370,16 @@ def assertNotText(self, driver, target, value=u''): raise def assertValue(self, driver, target, value=u''): + """ + Assert that the value attribute of a specified web element matches the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be asserted. + value (str, optional): The expected value to compare against (default is an empty string). + + """ + try: target_value = find_element(driver, target).get_attribute('value') print(' assertValue target value =' + repr(target_value)) @@ -227,6 +394,16 @@ def assertValue(self, driver, target, value=u''): raise def assertNotValue(self, driver, target, value=u''): + """ + Assert that the value attribute of a specified web element does not match the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be asserted. + value (str, optional): The expected value to compare against (default is an empty string). + + """ + try: target_value = find_element(driver, target).get_attribute('value') print(' assertNotValue target value =' + repr(target_value)) @@ -241,6 +418,16 @@ def assertNotValue(self, driver, target, value=u''): raise def verifyValue(self, driver, target, value=u''): + """ + Verify that the value attribute of a specified web element matches the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be verified. + value (str, optional): The expected value to compare against (default is an empty string). + + """ + try: target_value = find_element(driver, target).get_attribute('value') print(' verifyValue target value =' + repr(target_value)) @@ -254,14 +441,19 @@ def verifyValue(self, driver, target, value=u''): ) raise - def selectWindow(self, driver, window): - pass - class SeleniumTestSuite(object): """A Selenium Test Suite""" def __init__(self, filename, callback=None): + """ + Initialize a SeleniumTestSuite instance. + + Args: + filename (str): The name of the test suite JSON file. + callback (callable, optional): A callback function to be executed after each test (default is None). + + """ self.filename = filename self.callback = callback @@ -274,7 +466,14 @@ def __init__(self, filename, callback=None): for test_data in json_data.get('tests', [])] def run(self, driver, url): - print("ICI AVEC : ", driver, url) + """ + Run the Selenium test suite with the specified WebDriver instance and URL. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + url (str): The base URL to be used for the tests. + + """ for test_title, test_data in self.tests: try: test = SeleniumTestCase(test_data, self.callback) @@ -284,6 +483,13 @@ def run(self, driver, url): raise def __repr__(self): + """ + Return a string representation of the SeleniumTestSuite. + + Returns: + str: A string representation of the test suite, including its title and test titles. + + """ test_titles = '\n'.join([title for title, _ in self.tests]) return f'{self.title}\n{test_titles}' @@ -294,26 +500,26 @@ class PodSeleniumTests(StaticLiveServerTestCase): fixtures = ["initial_data.json"] def setUp(self): - """Set up the tests.""" + """Set up the tests and initialize custom test data.""" self.initialize_data() @classmethod def setUpClass(cls): - """Create the driver for all selenium tests.""" + """Create the WebDriver for all Selenium tests.""" super().setUpClass() cls.selenium = WebDriver() cls.selenium.implicitly_wait(10) @classmethod def tearDownClass(cls): - """Close the driver used.""" + """Close the WebDriver used.""" cls.selenium.quit() super().tearDownClass() @override_settings(DEBUG=False) def test_selenium_suites(self): - """Run Selenium Test Suites from Side files in all installed apps""" - run_all_tests() + """Run Selenium Test Suites from Side files in all installed apps.""" + self.run_all_tests() def initialize_data(self): """Initialize custom test data.""" @@ -326,53 +532,78 @@ def initialize_data(self): type=Type.objects.get(id=1), ) + def run_suite(self, suite_name, suite_url): + """ + Run a Selenium test suite with the specified name and URL. -def run_suite(suite_name, suite_url): - print(f'Running test suite: {suite_name}') + Args: + suite_name (str): The name of the test suite JSON file. + suite_url (str): The base URL for the test suite. - with open(suite_name, 'r') as json_file: - suite_data = json.load(json_file) - suite_tests = suite_data.get('tests', []) - - for test_data in suite_tests: - test_case = SeleniumTestCase(test_data) - try: - test_case.run(PodSeleniumTests.selenium, suite_url) - except Exception as e: - PodSeleniumTests.selenium.save_screenshot(f'{suite_name}-error_screen.png') - print(f'Error in suite {suite_name}: {e}') + """ + print(f'Running test suite: {suite_name}') + with open(suite_name, 'r') as json_file: + suite_data = json.load(json_file) + suite_tests = suite_data.get('tests', []) -def run_all_tests(): - for app in settings.INSTALLED_APPS: - app_module = __import__(app, fromlist=["integration_tests"]) - integration_tests_dir = os.path.join( - os.path.dirname(app_module.__file__), "integration_tests" - ) - if os.path.exists(integration_tests_dir): - tests_dir = os.path.join(integration_tests_dir, "tests") - if os.path.exists(tests_dir): - print(f"Running Selenium tests in {app}...") - run_tests_in_directory(tests_dir) - print("FIN de TOUS les TESTS") - - -def run_tests_in_directory(directory): - for root, dirs, files in os.walk(directory): - for filename in files: - if filename.endswith('.side'): - suite_name = os.path.join(root, filename) - run_single_suite(suite_name) - - -def run_single_suite(suite_name): - with Display(visible=0, size=(1920, 1080)): - with WebDriver() as driver: - PodSeleniumTests.selenium = driver - PodSeleniumTests.setUpClass() + for test_data in suite_tests: + test_case = SeleniumTestCase(test_data) try: - suite_url = "http://localhost:8080" - run_suite(suite_name, suite_url) + test_case.run(PodSeleniumTests.selenium, suite_url) except Exception as e: + PodSeleniumTests.selenium.save_screenshot( + f'{suite_name}-error_screen.png') print(f'Error in suite {suite_name}: {e}') - PodSeleniumTests.tearDownClass() + + def run_all_tests(self): + """ + Run Selenium test suites in all installed apps. + + This method searches for test suites in integration_tests/tests directories of all installed apps and runs them. + + """ + for app in settings.INSTALLED_APPS: + app_module = __import__(app, fromlist=["integration_tests"]) + integration_tests_dir = os.path.join( + os.path.dirname(app_module.__file__), "integration_tests" + ) + if os.path.exists(integration_tests_dir): + tests_dir = os.path.join(integration_tests_dir, "tests") + if os.path.exists(tests_dir): + print(f"Running Selenium tests in {app}...") + self.run_tests_in_directory(tests_dir) + print("All tests are DONE") + + def run_tests_in_directory(self, directory): + """ + Run Selenium test suites in the specified directory. + + Args: + directory (str): The directory containing test suites. + + """ + for root, dirs, files in os.walk(directory): + for filename in files: + if filename.endswith('.side'): + suite_name = os.path.join(root, filename) + self.run_single_suite(suite_name) + + def run_single_suite(self, suite_name): + """ + Run a single Selenium test suite. + + Args: + suite_name (str): The name of the test suite JSON file to run. + + """ + with Display(visible=0, size=(1920, 1080)): + with WebDriver() as driver: + PodSeleniumTests.selenium = driver + PodSeleniumTests.setUpClass() + try: + suite_url = "http://localhost:8080" + self.run_suite(suite_name, suite_url) + except Exception as e: + print(f'Error in suite {suite_name}: {e}') + PodSeleniumTests.tearDownClass() diff --git a/pod/main/integration_tests/tests/cookies.side b/pod/main/integration_tests/tests/cookies.side index b44c38231e..105d25afe0 100644 --- a/pod/main/integration_tests/tests/cookies.side +++ b/pod/main/integration_tests/tests/cookies.side @@ -16,10 +16,7 @@ }, { "id": "pod-accept-cookies", "comment": "Valide les cookies du navigateur", - "command": "click", - "target": "id=okcookie", - "targets": [], - "value": "" + "command": "cookies_commands" }] }], "suites": [{ From 81a134e844ab38443e50588edfac6ab0fa493ae5 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Fri, 6 Oct 2023 14:19:13 +0200 Subject: [PATCH 40/52] Improve code --- .github/workflows/code_formatting.yml | 6 +++--- .github/workflows/pod.yml | 2 +- pod/main/configuration.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 80591d1270..7ad3054ced 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -68,14 +68,14 @@ jobs: # Compile lang files - name: Compile lang files in fr and nl - run: python manage.py compilemessages -l fr -l nl + run: make compilelang - name: Check for modified files - id: compilation-git-check + id: compilelang-git-check run: echo modified=$(if git diff --quiet; then echo "false"; else echo "true"; fi) >> $GITHUB_OUTPUT - name: Push lang compilation changes - if: steps.compilation-git-check.outputs.modified == 'true' + if: steps.compilelang-git-check.outputs.modified == 'true' run: | git commit -am "Compile lang files" git push diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index 120228ddf6..6ac2400f3a 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -17,7 +17,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.9'] # ['3.8', '3.9', '3.10'] + python-version: ['3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v3 diff --git a/pod/main/configuration.json b/pod/main/configuration.json index fffbb32846..316bffffb0 100644 --- a/pod/main/configuration.json +++ b/pod/main/configuration.json @@ -4035,7 +4035,7 @@ "pod_version_init": "3.1" }, "USE_DEBUG_TOOLBAR": { - "default_value": true, + "default_value": false, "description": { "en": [ "A boolean value that enables or disables the debugging tool.
", From 5aff4ede0bc24afc15a1e26dbee4af5b16510aee Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Fri, 6 Oct 2023 14:53:14 +0200 Subject: [PATCH 41/52] Styling & Linting --- .github/workflows/code_formatting.yml | 7 +- .../selenium_pod_integration_tests.py | 230 ++++++------ pod/main/integration_tests/tests/cookies.side | 53 +-- pod/main/integration_tests/tests/lang.side | 203 ++++++----- pod/main/integration_tests/tests/search.side | 167 +++++---- pod/settings.py | 6 +- .../integration_tests/tests/comment.side | 334 ++++++++++-------- 7 files changed, 544 insertions(+), 456 deletions(-) diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 7ad3054ced..8022a1e95c 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -17,9 +17,12 @@ jobs: git config user.email github-actions@github.com git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} - # Prettify js code with prettier + # Prettify .js | .json | .side files with prettier - name: Running prettier - run: npx prettier --write pod/*/static/**/*.js + run: | + npx prettier --write pod/*/static/**/*.js + npx prettier --write pod/*/static/**/*.json + npx prettier --write --parser json pod/**/*.side - name: Check for modified files id: prettier-git-check diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 0fa4eec367..0b95ccf3d0 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -17,6 +17,35 @@ target_cache = {} +def parse_target(target): + """ + Parse the target string to determine the Selenium locator type and value. + + Args: + target (str): The target element identifier, which can start with 'link=', '//', 'xpath=', 'css=', 'id=', or 'name='. + + Returns: + tuple: A tuple containing the Selenium locator type (e.g., By.LINK_TEXT, By.XPATH, By.CSS_SELECTOR, By.ID, By.NAME) and the corresponding value. + + Returns: + tuple: A tuple containing the Selenium locator type and the corresponding value, or (None, None) if the target identifier format is not recognized. + """ + if target.startswith("link="): + return By.LINK_TEXT, target[5:] + elif target.startswith("//"): + return By.XPATH, target + elif target.startswith("xpath="): + return By.XPATH, target[6:] + elif target.startswith("css="): + return By.CSS_SELECTOR, target[4:] + elif target.startswith("id="): + return By.ID, target[3:] + elif target.startswith("name="): + return By.NAME, target[5:] + else: + return None, None + + def find_element(driver, target): """ Find and return a web element using various locator strategies (e.g., By.ID, By.XPATH, By.CSS_SELECTOR). @@ -31,34 +60,21 @@ def find_element(driver, target): Raises: NoSuchElementException: If the specified element is not found on the web page. Exception: If the target identifier format is not recognized. - """ if target in target_cache: target = target_cache[target] - if target.startswith('link='): + locator, value = parse_target(target) + + if locator is not None: try: - return driver.find_element(By.LINK_TEXT, target[5:]) + return driver.find_element(locator, value) except NoSuchElementException: - result = driver.find_element(By.LINK_TEXT, target[5:].lower()) - target_cache[target] = 'link=' + target[5:].lower() - msg = ' label %s is being cached as %s' - print(msg, target, target_cache[target]) + result = driver.find_element(locator, value.lower()) + target_cache[target] = f"{locator}={value.lower()}" + msg = f"label {value} is being cached as {target_cache[target]}" + print(msg) return result - elif target.startswith('//'): - return driver.find_element(By.XPATH, target) - - elif target.startswith('xpath='): - return driver.find_element(By.XPATH, target[6:]) - - elif target.startswith('css='): - return driver.find_element(By.CSS_SELECTOR, target[4:]) - - elif target.startswith('id='): - return driver.find_element(By.ID, target[3:]) - - elif target.startswith('name='): - return driver.find_element(By.NAME, target[5:]) else: direct = ( driver.find_element(By.NAME, target) @@ -67,7 +83,7 @@ def find_element(driver, target): ) if direct: return direct - raise Exception('Don\'t know how to find %s' % target) + raise Exception("Don't know how to find %s" % target) class SeleniumTestCase(object): @@ -80,7 +96,6 @@ def __init__(self, test_data, callback=None): Args: test_data (dict): Test case data, including name, URL, and commands. callback (callable, optional): Callback function to be executed after the test. - """ self.test_data = test_data self.callback = callback @@ -94,7 +109,6 @@ def run(self, driver, url): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. url (str): The base URL for the test case. - """ self.base_url = url print(f'Running test: {self.test_data["name"]}') @@ -106,7 +120,7 @@ def run(self, driver, url): args.append(target) if value is not None: args.append(value) - print(f' {method_name} {args}') + print(f" {method_name} {args}") method(*args) if self.callback: self.callback(driver.page_source) @@ -118,11 +132,12 @@ def cookies_commands(self, driver, url): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. url (str): The base URL for the test case. - """ - with open('pod/main/integration_tests/commands/cookies_commands.side', 'r') as side_file: + with open( + "pod/main/integration_tests/commands/cookies_commands.side", "r" + ) as side_file: side_data = json.load(side_file) - self.test_data['commands'] = side_data.get('commands', []) + self.test_data["commands"] = side_data.get("commands", []) self.run(driver, url) def open(self, driver, target): @@ -132,7 +147,6 @@ def open(self, driver, target): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The URL to open. - """ driver.get(self.base_url + target) @@ -143,7 +157,6 @@ def click(self, driver, target): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element to be clicked. - """ element = find_element(driver, target) driver.execute_script("arguments[0].scrollIntoView();", element) @@ -156,11 +169,10 @@ def clickAndWait(self, driver, target): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element to be clicked. - """ self.click(driver, target) - def type(self, driver, target, text=''): + def type(self, driver, target, text=""): """ Simulate typing text into an input field on a web page. @@ -182,13 +194,12 @@ def select(self, driver, target, value): driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the dropdown element. value (str): The option value to be selected. - """ element = find_element(driver, target) - if value.startswith('label='): + if value.startswith("label="): Select(element).select_by_visible_text(value[6:]) else: - msg = "Don\'t know how to select %s on %s" + msg = "Don't know how to select %s on %s" raise Exception(msg % (value, target)) def verifyTextPresent(self, driver, text): @@ -198,18 +209,12 @@ def verifyTextPresent(self, driver, text): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. text (str): The text to be verified for presence. - """ try: source = driver.page_source assert bool(text in source) - except: - print( - 'verifyTextPresent: ', - repr(text), - 'not present in', - repr(source) - ) + except Exception: + print("verifyTextPresent: ", repr(text), "not present in", repr(source)) raise def verifyTextNotPresent(self, driver, text): @@ -219,16 +224,11 @@ def verifyTextNotPresent(self, driver, text): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. text (str): The text to be verified for absence. - """ try: assert not bool(text in driver.page_source) - except: - print( - 'verifyNotTextPresent: ', - repr(text), - 'present' - ) + except Exception: + print("verifyNotTextPresent: ", repr(text), "present") raise def assertElementPresent(self, driver, target): @@ -238,12 +238,11 @@ def assertElementPresent(self, driver, target): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element to be verified for presence. - """ try: assert bool(find_element(driver, target)) - except: - print('assertElementPresent: ', repr(target), 'not present') + except Exception: + print("assertElementPresent: ", repr(target), "not present") raise def verifyElementPresent(self, driver, target): @@ -253,12 +252,11 @@ def verifyElementPresent(self, driver, target): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element to be verified for presence. - """ try: assert bool(find_element(driver, target)) - except: - print('verifyElementPresent: ', repr(target), 'not present') + except Exception: + print("verifyElementPresent: ", repr(target), "not present") raise def verifyElementNotPresent(self, driver, target): @@ -268,7 +266,6 @@ def verifyElementNotPresent(self, driver, target): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element to be verified for absence. - """ present = True @@ -279,8 +276,8 @@ def verifyElementNotPresent(self, driver, target): try: assert not present - except: - print('verifyElementNotPresent: ', repr(target), 'present') + except Exception: + print("verifyElementNotPresent: ", repr(target), "present") raise def waitForTextPresent(self, driver, text): @@ -290,13 +287,12 @@ def waitForTextPresent(self, driver, text): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. text (str): The text to wait for in the page source. - """ try: assert bool(text in driver.page_source) - except: - print('waitForTextPresent: ', repr(text), 'not present') + except Exception: + print("waitForTextPresent: ", repr(text), "not present") raise def waitForTextNotPresent(self, driver, text): @@ -306,16 +302,15 @@ def waitForTextNotPresent(self, driver, text): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. text (str): The text to wait for to be absent in the page source. - """ try: assert not bool(text in driver.page_source) - except: - print('waitForTextNotPresent: ', repr(text), 'present') + except Exception: + print("waitForTextNotPresent: ", repr(text), "present") raise - def assertText(self, driver, target, value=u''): + def assertText(self, driver, target, value=""): """ Assert that the text of a specified web element matches the expected value. @@ -323,26 +318,25 @@ def assertText(self, driver, target, value=u''): driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element whose text needs to be asserted. value (str, optional): The expected text value to compare against (default is an empty string). - """ try: target_value = find_element(driver, target).text - print(' assertText target value =' + repr(target_value)) - if value.startswith('exact:'): - assert target_value == value[len('exact:'):] + print(" assertText target value =" + repr(target_value)) + if value.startswith("exact:"): + assert target_value == value[len("exact:") :] else: assert target_value == value - except: + except Exception: print( - 'assertText: ', + "assertText: ", repr(target), - repr(find_element(driver, target).get_attribute('value')), + repr(find_element(driver, target).get_attribute("value")), repr(value), ) raise - def assertNotText(self, driver, target, value=u''): + def assertNotText(self, driver, target, value=""): """ Assert that the text of a specified web element does not match the expected value. @@ -350,26 +344,25 @@ def assertNotText(self, driver, target, value=u''): driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element whose text needs to be asserted. value (str, optional): The expected text value to compare against (default is an empty string). - """ try: target_value = find_element(driver, target).text - print(' assertNotText target value =' + repr(target_value)) - if value.startswith('exact:'): - assert target_value != value[len('exact:'):] + print(" assertNotText target value =" + repr(target_value)) + if value.startswith("exact:"): + assert target_value != value[len("exact:") :] else: assert target_value != value - except: + except Exception: print( - 'assertNotText: ', + "assertNotText: ", repr(target), - repr(find_element(driver, target).get_attribute('value')), + repr(find_element(driver, target).get_attribute("value")), repr(value), ) raise - def assertValue(self, driver, target, value=u''): + def assertValue(self, driver, target, value=""): """ Assert that the value attribute of a specified web element matches the expected value. @@ -377,23 +370,22 @@ def assertValue(self, driver, target, value=u''): driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element whose value attribute needs to be asserted. value (str, optional): The expected value to compare against (default is an empty string). - """ try: - target_value = find_element(driver, target).get_attribute('value') - print(' assertValue target value =' + repr(target_value)) + target_value = find_element(driver, target).get_attribute("value") + print(" assertValue target value =" + repr(target_value)) assert target_value == value - except: + except Exception: print( - 'assertValue: ', + "assertValue: ", repr(target), - repr(find_element(driver, target).get_attribute('value')), + repr(find_element(driver, target).get_attribute("value")), repr(value), ) raise - def assertNotValue(self, driver, target, value=u''): + def assertNotValue(self, driver, target, value=""): """ Assert that the value attribute of a specified web element does not match the expected value. @@ -401,23 +393,22 @@ def assertNotValue(self, driver, target, value=u''): driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element whose value attribute needs to be asserted. value (str, optional): The expected value to compare against (default is an empty string). - """ try: - target_value = find_element(driver, target).get_attribute('value') - print(' assertNotValue target value =' + repr(target_value)) + target_value = find_element(driver, target).get_attribute("value") + print(" assertNotValue target value =" + repr(target_value)) assert target_value != value - except: + except Exception: print( - 'assertNotValue: ', + "assertNotValue: ", repr(target), repr(target_value), repr(value), ) raise - def verifyValue(self, driver, target, value=u''): + def verifyValue(self, driver, target, value=""): """ Verify that the value attribute of a specified web element matches the expected value. @@ -425,18 +416,17 @@ def verifyValue(self, driver, target, value=u''): driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element whose value attribute needs to be verified. value (str, optional): The expected value to compare against (default is an empty string). - """ try: - target_value = find_element(driver, target).get_attribute('value') - print(' verifyValue target value =' + repr(target_value)) + target_value = find_element(driver, target).get_attribute("value") + print(" verifyValue target value =" + repr(target_value)) assert target_value == value - except: + except Exception: print( - 'verifyValue: ', + "verifyValue: ", repr(target), - repr(find_element(driver, target).get_attribute('value')), + repr(find_element(driver, target).get_attribute("value")), repr(value), ) raise @@ -452,18 +442,19 @@ def __init__(self, filename, callback=None): Args: filename (str): The name of the test suite JSON file. callback (callable, optional): A callback function to be executed after each test (default is None). - """ self.filename = filename self.callback = callback self.tests = [] - with open(filename, 'r') as json_file: + with open(filename, "r") as json_file: json_data = json.load(json_file) - self.title = json_data.get('name', 'Untitled Suite') - self.tests = [(test_data.get('name', 'Untitled Test'), test_data) - for test_data in json_data.get('tests', [])] + self.title = json_data.get("name", "Untitled Suite") + self.tests = [ + (test_data.get("name", "Untitled Test"), test_data) + for test_data in json_data.get("tests", []) + ] def run(self, driver, url): """ @@ -472,14 +463,13 @@ def run(self, driver, url): Args: driver (WebDriver): The WebDriver instance for interacting with the web page. url (str): The base URL to be used for the tests. - """ for test_title, test_data in self.tests: try: test = SeleniumTestCase(test_data, self.callback) test.run(driver, url) except Exception as e: - print(f'Error in {test_title} ({test.filename}): {e}') + print(f"Error in {test_title} ({test.filename}): {e}") raise def __repr__(self): @@ -488,10 +478,9 @@ def __repr__(self): Returns: str: A string representation of the test suite, including its title and test titles. - """ - test_titles = '\n'.join([title for title, _ in self.tests]) - return f'{self.title}\n{test_titles}' + test_titles = "\n".join([title for title, _ in self.tests]) + return f"{self.title}\n{test_titles}" class PodSeleniumTests(StaticLiveServerTestCase): @@ -539,13 +528,12 @@ def run_suite(self, suite_name, suite_url): Args: suite_name (str): The name of the test suite JSON file. suite_url (str): The base URL for the test suite. - """ - print(f'Running test suite: {suite_name}') + print(f"Running test suite: {suite_name}") - with open(suite_name, 'r') as json_file: + with open(suite_name, "r") as json_file: suite_data = json.load(json_file) - suite_tests = suite_data.get('tests', []) + suite_tests = suite_data.get("tests", []) for test_data in suite_tests: test_case = SeleniumTestCase(test_data) @@ -553,15 +541,15 @@ def run_suite(self, suite_name, suite_url): test_case.run(PodSeleniumTests.selenium, suite_url) except Exception as e: PodSeleniumTests.selenium.save_screenshot( - f'{suite_name}-error_screen.png') - print(f'Error in suite {suite_name}: {e}') + f"{suite_name}-error_screen.png" + ) + print(f"Error in suite {suite_name}: {e}") def run_all_tests(self): """ Run Selenium test suites in all installed apps. This method searches for test suites in integration_tests/tests directories of all installed apps and runs them. - """ for app in settings.INSTALLED_APPS: app_module = __import__(app, fromlist=["integration_tests"]) @@ -581,11 +569,10 @@ def run_tests_in_directory(self, directory): Args: directory (str): The directory containing test suites. - """ for root, dirs, files in os.walk(directory): for filename in files: - if filename.endswith('.side'): + if filename.endswith(".side"): suite_name = os.path.join(root, filename) self.run_single_suite(suite_name) @@ -595,7 +582,6 @@ def run_single_suite(self, suite_name): Args: suite_name (str): The name of the test suite JSON file to run. - """ with Display(visible=0, size=(1920, 1080)): with WebDriver() as driver: @@ -605,5 +591,5 @@ def run_single_suite(self, suite_name): suite_url = "http://localhost:8080" self.run_suite(suite_name, suite_url) except Exception as e: - print(f'Error in suite {suite_name}: {e}') + print(f"Error in suite {suite_name}: {e}") PodSeleniumTests.tearDownClass() diff --git a/pod/main/integration_tests/tests/cookies.side b/pod/main/integration_tests/tests/cookies.side index 105d25afe0..781b6663af 100644 --- a/pod/main/integration_tests/tests/cookies.side +++ b/pod/main/integration_tests/tests/cookies.side @@ -3,30 +3,37 @@ "version": "2.0", "name": "pod-front", "url": "http://localhost:9090/", - "tests": [{ - "id": "pod-accept-cookies", - "name": "cookies", - "commands": [{ - "id": "pod-open-home", - "comment": "Ouvre la page d'accueil", - "command": "open", - "target": "/", - "targets": [], - "value": "" - }, { + "tests": [ + { "id": "pod-accept-cookies", - "comment": "Valide les cookies du navigateur", - "command": "cookies_commands" - }] - }], - "suites": [{ - "id": "pod-front", - "name": "Esup Pod test selenium", - "persistSession": false, - "parallel": false, - "timeout": 300, - "tests": ["pod-accept-cookies"] - }], + "name": "cookies", + "commands": [ + { + "id": "pod-open-home", + "comment": "Ouvre la page d'accueil", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, + { + "id": "pod-accept-cookies", + "comment": "Valide les cookies du navigateur", + "command": "cookies_commands" + } + ] + } + ], + "suites": [ + { + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-accept-cookies"] + } + ], "urls": ["http://localhost:9090/"], "plugins": [] } diff --git a/pod/main/integration_tests/tests/lang.side b/pod/main/integration_tests/tests/lang.side index dd181e9976..af6a54315b 100644 --- a/pod/main/integration_tests/tests/lang.side +++ b/pod/main/integration_tests/tests/lang.side @@ -3,94 +3,121 @@ "version": "2.0", "name": "pod-front", "url": "http://localhost:9090/", - "tests": [{ - "id": "pod-change-lang", - "name": "lang", - "commands": [{ - "id": "pod-open-home", - "comment": "Ouvre la page d'accueil", - "command": "open", - "target": "/", - "targets": [], - "value": "" - }, { - "id": "pod-accept-cookies", - "comment": "Valide les cookies du navigateur", - "command": "cookies_commands" - }, { - "id": "pod-open-configuration", - "comment": "Ouvre la page de configuration", - "command": "click", - "target": "id=pod-param-buttons__button", - "targets": [ - ["id=pod-param-buttons__button", "id"], - ["css=#pod-param-buttons__button", "css:finder"], - ["xpath=//button[@id='pod-param-buttons__button']", "xpath:attributes"], - ["xpath=//li[@id='pod-param-buttons']/button", "xpath:idRelative"], - ["xpath=//nav/div/ul/li/button", "xpath:position"] - ], - "value": "" - }, { - "id": "pod-toggle-langue-menu", - "comment": "Sélectionne menu choix langue", - "command": "mouseOver", - "target": "id=pod-lang-select", - "targets": [ - ["id=pod-lang-select", "id"], - ["css=#pod-lang-select", "css:finder"], - ["xpath=//button[@id='pod-lang-select']", "xpath:attributes"], - ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/button", "xpath:idRelative"], - ["xpath=//div[2]/ul/li/div/div/button", "xpath:position"] - ], - "value": "" - }, { - "id": "pod-toggle-lang-select", - "comment": "Sélectionne la langue", - "command": "click", - "target": "id=pod-lang-select", - "targets": [ - ["id=pod-lang-select", "id"], - ["css=#pod-lang-select", "css:finder"], - ["xpath=//button[@id='pod-lang-select']", "xpath:attributes"], - ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/button", "xpath:idRelative"], - ["xpath=//div[2]/ul/li/div/div/button", "xpath:position"] - ], - "value": "" - }, { - "id": "pod-toggle-lang-deselect", - "comment": "Déselectionne langue actuelle", - "command": "mouseOut", - "target": "id=pod-lang-select", - "targets": [ - ["id=pod-lang-select", "id"], - ["css=#pod-lang-select", "css:finder"], - ["xpath=//button[@id='pod-lang-select']", "xpath:attributes"], - ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/button", "xpath:idRelative"], - ["xpath=//div[2]/ul/li/div/div/button", "xpath:position"] - ], - "value": "" - }, { - "id": "pod-toggle-lang-confirmation", - "comment": "Sélectionne langue ciblée", - "command": "click", - "target": "css=.dropdown-item", - "targets": [ - ["css=.dropdown-item", "css:finder"], - ["xpath=//input[@value='English (en)']", "xpath:attributes"], - ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/div/form/input[3]", "xpath:idRelative"], - ["xpath=//input[3]", "xpath:position"] - ], - "value": "" - }] - }], - "suites": [{ - "id": "pod-front", - "name": "Esup Pod test selenium", - "persistSession": false, - "parallel": false, - "timeout": 300, - "tests": ["pod-change-lang"] - }], + "tests": [ + { + "id": "pod-change-lang", + "name": "lang", + "commands": [ + { + "id": "pod-open-home", + "comment": "Ouvre la page d'accueil", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, + { + "id": "pod-accept-cookies", + "comment": "Valide les cookies du navigateur", + "command": "cookies_commands" + }, + { + "id": "pod-open-configuration", + "comment": "Ouvre la page de configuration", + "command": "click", + "target": "id=pod-param-buttons__button", + "targets": [ + ["id=pod-param-buttons__button", "id"], + ["css=#pod-param-buttons__button", "css:finder"], + [ + "xpath=//button[@id='pod-param-buttons__button']", + "xpath:attributes" + ], + ["xpath=//li[@id='pod-param-buttons']/button", "xpath:idRelative"], + ["xpath=//nav/div/ul/li/button", "xpath:position"] + ], + "value": "" + }, + { + "id": "pod-toggle-langue-menu", + "comment": "Sélectionne menu choix langue", + "command": "mouseOver", + "target": "id=pod-lang-select", + "targets": [ + ["id=pod-lang-select", "id"], + ["css=#pod-lang-select", "css:finder"], + ["xpath=//button[@id='pod-lang-select']", "xpath:attributes"], + [ + "xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/button", + "xpath:idRelative" + ], + ["xpath=//div[2]/ul/li/div/div/button", "xpath:position"] + ], + "value": "" + }, + { + "id": "pod-toggle-lang-select", + "comment": "Sélectionne la langue", + "command": "click", + "target": "id=pod-lang-select", + "targets": [ + ["id=pod-lang-select", "id"], + ["css=#pod-lang-select", "css:finder"], + ["xpath=//button[@id='pod-lang-select']", "xpath:attributes"], + [ + "xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/button", + "xpath:idRelative" + ], + ["xpath=//div[2]/ul/li/div/div/button", "xpath:position"] + ], + "value": "" + }, + { + "id": "pod-toggle-lang-deselect", + "comment": "Déselectionne langue actuelle", + "command": "mouseOut", + "target": "id=pod-lang-select", + "targets": [ + ["id=pod-lang-select", "id"], + ["css=#pod-lang-select", "css:finder"], + ["xpath=//button[@id='pod-lang-select']", "xpath:attributes"], + [ + "xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/button", + "xpath:idRelative" + ], + ["xpath=//div[2]/ul/li/div/div/button", "xpath:position"] + ], + "value": "" + }, + { + "id": "pod-toggle-lang-confirmation", + "comment": "Sélectionne langue ciblée", + "command": "click", + "target": "css=.dropdown-item", + "targets": [ + ["css=.dropdown-item", "css:finder"], + ["xpath=//input[@value='English (en)']", "xpath:attributes"], + [ + "xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li/div/div/div/form/input[3]", + "xpath:idRelative" + ], + ["xpath=//input[3]", "xpath:position"] + ], + "value": "" + } + ] + } + ], + "suites": [ + { + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-change-lang"] + } + ], "urls": ["http://localhost:9090/"], "plugins": [] } diff --git a/pod/main/integration_tests/tests/search.side b/pod/main/integration_tests/tests/search.side index c8313566d7..0282ddfa35 100644 --- a/pod/main/integration_tests/tests/search.side +++ b/pod/main/integration_tests/tests/search.side @@ -3,84 +3,95 @@ "version": "2.0", "name": "pod-front", "url": "http://localhost:9090/", - "tests": [{ - "id": "pod-searchbar", - "name": "search", - "commands": [{ - "id": "pod-open-home", - "comment": "Ouvre la page d'accueil", - "command": "open", - "target": "/", - "targets": [], - "value": "" - }, { - "id": "pod-accept-cookies", - "comment": "Valide les cookies du navigateur", - "command": "cookies_commands" - }, { - "id": "pod-search", - "comment": "Sélectionne barre recherche", - "command": "click", - "target": "id=s", - "targets": [ - ["id=s", "id"], - ["name=q", "name"], - ["css=#s", "css:finder"], - ["xpath=//input[@id='s']", "xpath:attributes"], - ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], - ["xpath=//input", "xpath:position"] - ], - "value": "" - }, { - "id": "pod-search-type", - "comment": "Tape mot clé", - "command": "type", - "target": "id=s", - "targets": [ - ["id=s", "id"], - ["name=q", "name"], - ["css=#s", "css:finder"], - ["xpath=//input[@id='s']", "xpath:attributes"], - ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], - ["xpath=//input", "xpath:position"] - ], - "value": "pas de résultat attendu" - }, { - "id": "pod-search-enter", - "comment": "Appuie touche entrée", - "command": "sendKeys", - "target": "id=s", - "targets": [ - ["id=s", "id"], - ["name=q", "name"], - ["css=#s", "css:finder"], - ["xpath=//input[@id='s']", "xpath:attributes"], - ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], - ["xpath=//input", "xpath:position"] - ], - "value": "${KEY_ENTER}" - }, { - "id": "pod-accueil-ariane", - "comment": "Retour à la page d'accueil via logo pod", - "command": "click", - "target": "xpath=//div[@id='nav-mainbar']/a/strong", - "targets": [ - ["css=strong", "css:finder"], - ["xpath=//div[@id='nav-mainbar']/a/strong", "xpath:idRelative"], - ["xpath=//strong", "xpath:position"], - ["xpath=//strong[contains(.,'Lille.Pod')]", "xpath:innerText"] - ], - "value": "" - }] - }], - "suites": [{ - "id": "pod-front", - "name": "Esup Pod test selenium", - "persistSession": false, - "parallel": false, - "timeout": 300, - "tests": ["pod-searchbar"] - }], + "tests": [ + { + "id": "pod-searchbar", + "name": "search", + "commands": [ + { + "id": "pod-open-home", + "comment": "Ouvre la page d'accueil", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, + { + "id": "pod-accept-cookies", + "comment": "Valide les cookies du navigateur", + "command": "cookies_commands" + }, + { + "id": "pod-search", + "comment": "Sélectionne barre recherche", + "command": "click", + "target": "id=s", + "targets": [ + ["id=s", "id"], + ["name=q", "name"], + ["css=#s", "css:finder"], + ["xpath=//input[@id='s']", "xpath:attributes"], + ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, + { + "id": "pod-search-type", + "comment": "Tape mot clé", + "command": "type", + "target": "id=s", + "targets": [ + ["id=s", "id"], + ["name=q", "name"], + ["css=#s", "css:finder"], + ["xpath=//input[@id='s']", "xpath:attributes"], + ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "pas de résultat attendu" + }, + { + "id": "pod-search-enter", + "comment": "Appuie touche entrée", + "command": "sendKeys", + "target": "id=s", + "targets": [ + ["id=s", "id"], + ["name=q", "name"], + ["css=#s", "css:finder"], + ["xpath=//input[@id='s']", "xpath:attributes"], + ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, + { + "id": "pod-accueil-ariane", + "comment": "Retour à la page d'accueil via logo pod", + "command": "click", + "target": "xpath=//div[@id='nav-mainbar']/a/strong", + "targets": [ + ["css=strong", "css:finder"], + ["xpath=//div[@id='nav-mainbar']/a/strong", "xpath:idRelative"], + ["xpath=//strong", "xpath:position"], + ["xpath=//strong[contains(.,'Lille.Pod')]", "xpath:innerText"] + ], + "value": "" + } + ] + } + ], + "suites": [ + { + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-searchbar"] + } + ], "urls": ["http://localhost:9090/"], "plugins": [] } diff --git a/pod/settings.py b/pod/settings.py index 47c81e1cdb..e83e61b002 100644 --- a/pod/settings.py +++ b/pod/settings.py @@ -459,7 +459,11 @@ def update_settings(local_settings): for variable in the_update_settings: locals()[variable] = the_update_settings[variable] -if locals()["DEBUG"] is True and importlib.util.find_spec("debug_toolbar") is not None and locals()["USE_DEBUG_TOOLBAR"]: +if ( + locals()["DEBUG"] is True + and importlib.util.find_spec("debug_toolbar") is not None + and locals()["USE_DEBUG_TOOLBAR"] +): INSTALLED_APPS.append("debug_toolbar") MIDDLEWARE = [ "debug_toolbar.middleware.DebugToolbarMiddleware", diff --git a/pod/video/integration_tests/tests/comment.side b/pod/video/integration_tests/tests/comment.side index 557ac4f6d2..2e4c09c5f9 100644 --- a/pod/video/integration_tests/tests/comment.side +++ b/pod/video/integration_tests/tests/comment.side @@ -3,148 +3,198 @@ "version": "2.0", "name": "pod-front", "url": "http://localhost:9090/", - "tests": [{ - "id": "pod-add-delete-comment", - "name": "comment", - "commands": [{ - "id": "pod-accept-cookies", - "comment": "Valide les cookies du navigateur", - "command": "cookies_commands" - }, { - "id": "pod-open-video-page", - "comment": "Ouvre la page de la vidéo", - "command": "open", - "target": "video/1-first-video/", - "targets": [], - "value": "" - }, { - "id": "pod-target-comment", - "comment": "Cible la zone de commentaire", - "command": "click", - "target": "id=comment", - "targets": [ - ["id=comment", "id"], - ["name=new_parent_comment", "name"], - ["css=#comment", "css:finder"], - ["xpath=//textarea[@id='comment']", "xpath:attributes"], - ["xpath=//div[@id='pod_comment_wrapper']/div/div/form/div/textarea", "xpath:idRelative"], - ["xpath=//form/div/textarea", "xpath:position"] - ], - "value": "" - }, { - "id": "pod-write-comment", - "comment": "écrit dans la zone de commentaire", - "command": "type", - "target": "id=comment", - "targets": [ - ["id=comment", "id"], - ["name=new_parent_comment", "name"], - ["css=#comment", "css:finder"], - ["xpath=//textarea[@id='comment']", "xpath:attributes"], - ["xpath=//div[@id='pod_comment_wrapper']/div/div/form/div/textarea", "xpath:idRelative"], - ["xpath=//form/div/textarea", "xpath:position"] - ], - "value": "Bonjour !" - }, { - "id": "pod-send-comment", - "comment": "clique sur le bouton \"commenter !\"", - "command": "click", - "target": "css=.add_comment_btn", - "targets": [ - ["css=.add_comment_btn", "css:finder"], - ["xpath=(//button[@type='submit'])[2]", "xpath:attributes"], - ["xpath=//div[@id='pod_comment_wrapper']/div/div/form/div[2]/button", "xpath:idRelative"], - ["xpath=//form/div[2]/button", "xpath:position"], - ["xpath=//button[contains(.,'Commenter !')]", "xpath:innerText"] - ], - "value": "" - }, { - "id": "pod-favorite-comment", - "comment": "Met en favori le commentaire", - "command": "click", - "target": "css=.unvoted > .bi", - "targets": [ - ["css=.unvoted > .bi", "css:finder"], - ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div/div/div/span/i", "xpath:idRelative"], - ["xpath=//div/span/i", "xpath:position"] - ], - "value": "" - }, { - "id": "pod-select-answer-zone", - "comment": "Sélectionne la zone de réponse", - "command": "click", - "target": "css=.comment_response_btn", - "targets": [ - ["css=.comment_response_btn", "css:finder"], - ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div/div[2]/span", "xpath:idRelative"], - ["xpath=//div[3]/div/div[2]/span", "xpath:position"], - ["xpath=//span[contains(.,'Répondre')]", "xpath:innerText"] - ], - "value": "" - }, { - "id": "pod-write-answer", - "comment": "écrit une réponse", - "command": "type", - "target": "name=new_comment", - "targets": [ - ["name=new_comment", "name"], - ["css=.add_comment > #comment_1695815656609", "css:finder"], - ["xpath=//textarea[@id='comment_1695815656609']", "xpath:attributes"], - ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div[2]/div/textarea", "xpath:idRelative"], - ["xpath=//div[2]/div/textarea", "xpath:position"] - ], - "value": "réponse" - }, { - "id": "pod-send-answer", - "comment": "envoi la réponse", - "command": "click", - "target": "css=.bi-send-fill", - "targets": [ - ["css=.bi-send-fill", "css:finder"], - ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div[2]/div/button/i", "xpath:idRelative"], - ["xpath=//div[3]/div[2]/div/button/i", "xpath:position"] - ], - "value": "" - }, { - "id": "pod-scroll-up", - "comment": "remonte la page (à garder ?)", - "command": "runScript", - "target": "window.scrollTo(0,720)", - "targets": [], - "value": "" - }, { - "id": "pod-select-delete-comment", - "comment": "Clique sur supprimer", - "command": "click", - "target": "css=.comment_actions:nth-child(4) .bi", - "targets": [ - ["css=.comment_actions:nth-child(4) .bi", "css:finder"], - ["xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div/div[4]/div/i", "xpath:idRelative"], - ["xpath=//div[4]/div/i", "xpath:position"] - ], - "value": "" - }, { - "id": "pod-delete-comment", - "comment": "Valide la suppression", - "command": "click", - "target": "css=.delete", - "targets": [ - ["css=.delete", "css:finder"], - ["xpath=//confirm-modal[@id='custom_element_confirm_modal']/div/div/div/div[3]/button", "xpath:idRelative"], - ["xpath=//confirm-modal/div/div/div/div[3]/button", "xpath:position"], - ["xpath=//button[contains(.,'Supprimer')]", "xpath:innerText"] - ], - "value": "" - }] - }], - "suites": [{ - "id": "pod-front", - "name": "Esup Pod test selenium", - "persistSession": false, - "parallel": false, - "timeout": 300, - "tests": ["pod-add-delete-comment"] - }], + "tests": [ + { + "id": "pod-add-delete-comment", + "name": "comment", + "commands": [ + { + "id": "pod-accept-cookies", + "comment": "Valide les cookies du navigateur", + "command": "cookies_commands" + }, + { + "id": "pod-open-video-page", + "comment": "Ouvre la page de la vidéo", + "command": "open", + "target": "video/1-first-video/", + "targets": [], + "value": "" + }, + { + "id": "pod-target-comment", + "comment": "Cible la zone de commentaire", + "command": "click", + "target": "id=comment", + "targets": [ + ["id=comment", "id"], + ["name=new_parent_comment", "name"], + ["css=#comment", "css:finder"], + ["xpath=//textarea[@id='comment']", "xpath:attributes"], + [ + "xpath=//div[@id='pod_comment_wrapper']/div/div/form/div/textarea", + "xpath:idRelative" + ], + ["xpath=//form/div/textarea", "xpath:position"] + ], + "value": "" + }, + { + "id": "pod-write-comment", + "comment": "écrit dans la zone de commentaire", + "command": "type", + "target": "id=comment", + "targets": [ + ["id=comment", "id"], + ["name=new_parent_comment", "name"], + ["css=#comment", "css:finder"], + ["xpath=//textarea[@id='comment']", "xpath:attributes"], + [ + "xpath=//div[@id='pod_comment_wrapper']/div/div/form/div/textarea", + "xpath:idRelative" + ], + ["xpath=//form/div/textarea", "xpath:position"] + ], + "value": "Bonjour !" + }, + { + "id": "pod-send-comment", + "comment": "clique sur le bouton \"commenter !\"", + "command": "click", + "target": "css=.add_comment_btn", + "targets": [ + ["css=.add_comment_btn", "css:finder"], + ["xpath=(//button[@type='submit'])[2]", "xpath:attributes"], + [ + "xpath=//div[@id='pod_comment_wrapper']/div/div/form/div[2]/button", + "xpath:idRelative" + ], + ["xpath=//form/div[2]/button", "xpath:position"], + ["xpath=//button[contains(.,'Commenter !')]", "xpath:innerText"] + ], + "value": "" + }, + { + "id": "pod-favorite-comment", + "comment": "Met en favori le commentaire", + "command": "click", + "target": "css=.unvoted > .bi", + "targets": [ + ["css=.unvoted > .bi", "css:finder"], + [ + "xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div/div/div/span/i", + "xpath:idRelative" + ], + ["xpath=//div/span/i", "xpath:position"] + ], + "value": "" + }, + { + "id": "pod-select-answer-zone", + "comment": "Sélectionne la zone de réponse", + "command": "click", + "target": "css=.comment_response_btn", + "targets": [ + ["css=.comment_response_btn", "css:finder"], + [ + "xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div/div[2]/span", + "xpath:idRelative" + ], + ["xpath=//div[3]/div/div[2]/span", "xpath:position"], + ["xpath=//span[contains(.,'Répondre')]", "xpath:innerText"] + ], + "value": "" + }, + { + "id": "pod-write-answer", + "comment": "écrit une réponse", + "command": "type", + "target": "name=new_comment", + "targets": [ + ["name=new_comment", "name"], + ["css=.add_comment > #comment_1695815656609", "css:finder"], + [ + "xpath=//textarea[@id='comment_1695815656609']", + "xpath:attributes" + ], + [ + "xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div[2]/div/textarea", + "xpath:idRelative" + ], + ["xpath=//div[2]/div/textarea", "xpath:position"] + ], + "value": "réponse" + }, + { + "id": "pod-send-answer", + "comment": "envoi la réponse", + "command": "click", + "target": "css=.bi-send-fill", + "targets": [ + ["css=.bi-send-fill", "css:finder"], + [ + "xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div[2]/div/button/i", + "xpath:idRelative" + ], + ["xpath=//div[3]/div[2]/div/button/i", "xpath:position"] + ], + "value": "" + }, + { + "id": "pod-scroll-up", + "comment": "remonte la page (à garder ?)", + "command": "runScript", + "target": "window.scrollTo(0,720)", + "targets": [], + "value": "" + }, + { + "id": "pod-select-delete-comment", + "comment": "Clique sur supprimer", + "command": "click", + "target": "css=.comment_actions:nth-child(4) .bi", + "targets": [ + ["css=.comment_actions:nth-child(4) .bi", "css:finder"], + [ + "xpath=//comment-element[@id='comment_1695815656609']/div/div[2]/div/div[3]/div/div[4]/div/i", + "xpath:idRelative" + ], + ["xpath=//div[4]/div/i", "xpath:position"] + ], + "value": "" + }, + { + "id": "pod-delete-comment", + "comment": "Valide la suppression", + "command": "click", + "target": "css=.delete", + "targets": [ + ["css=.delete", "css:finder"], + [ + "xpath=//confirm-modal[@id='custom_element_confirm_modal']/div/div/div/div[3]/button", + "xpath:idRelative" + ], + [ + "xpath=//confirm-modal/div/div/div/div[3]/button", + "xpath:position" + ], + ["xpath=//button[contains(.,'Supprimer')]", "xpath:innerText"] + ], + "value": "" + } + ] + } + ], + "suites": [ + { + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-add-delete-comment"] + } + ], "urls": ["http://localhost:9090/", "https://pod-test.univ-lille.fr/"], "plugins": [] } From fc58774511de3c72ade5f7d2543780517da0a5c8 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Thu, 12 Oct 2023 11:42:56 +0200 Subject: [PATCH 42/52] Rework SeleniumTestCase and it's execution --- .../selenium_pod_integration_tests.py | 189 ++++++++++++------ pod/main/integration_tests/tests/lang.side | 5 - pod/main/integration_tests/tests/search.side | 5 - .../integration_tests/tests/comment.side | 7 +- requirements-dev.txt | 1 - 5 files changed, 128 insertions(+), 79 deletions(-) diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 400aacef70..81ecc7f390 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -1,18 +1,24 @@ -from django.contrib.staticfiles.testing import StaticLiveServerTestCase -from selenium.webdriver.firefox.webdriver import WebDriver -from selenium.webdriver.common.by import By -from selenium.common.exceptions import NoSuchElementException +"""Esup-Pod integration tests. + +* run with 'python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests' +""" + import json import os -from pyvirtualdisplay import Display + from django.conf import settings -from selenium.webdriver.support.ui import Select -from django.test.utils import override_settings from django.contrib.auth.models import User +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from django.test.utils import override_settings +from pod.video_encode_transcript.encode import encode_video + +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By +from selenium.webdriver.firefox.webdriver import WebDriver +from selenium.webdriver.support.ui import Select + from pod.video.models import Video, Type -# python manage.py test -v 3 --settings=pod.main.test_settings \ -# pod.main.integration_tests.selenium_pod_integration_tests target_cache = {} @@ -112,20 +118,49 @@ def run(self, driver, url): """ self.base_url = url print(f'Running test: {self.test_data["name"]}') - for command in self.commands: - method_name, target, value = command - method = getattr(self, method_name) - args = [driver] - if target is not None: - args.append(target) - if value is not None: - args.append(value) - print(f" {method_name} {args}") - method(*args) + self.run_commands(driver, url) if self.callback: self.callback(driver.page_source) - def cookies_commands(self, driver, url): + def run_commands(self, driver, url): + COMMAND_MAPPING = { + "open": self.open, + "click": self.click, + "cookies_commands": self.cookies_commands, + "clickAndWait": self.clickAndWait, + "type": self.type, + "select": self.select, + "verifyTextPresent": self.verifyTextPresent, + "verifyTextNotPresent": self.verifyTextNotPresent, + "assertElementPresent": self.assertElementPresent, + "verifyElementPresent": self.verifyElementPresent, + "verifyElementNotPresent": self.verifyElementNotPresent, + "waitForTextPresent": self.waitForTextPresent, + "waitForTextNotPresent": self.waitForTextNotPresent, + "assertText": self.assertText, + "assertNotText": self.assertNotText, + "assertValue": self.assertValue, + "assertNotValue": self.assertNotValue, + "verifyValue": self.verifyValue, + "mouseOver": self.mouseOver, + "mouseOut": self.mouseOut, + "sendKeys": self.sendKeys, + } + + for command in self.test_data.get("commands", []): + command_name = command.get("command", "") + target = command.get("target", "") + value = command.get("value", "") + text = command.get("text", "") + + if command_name in COMMAND_MAPPING: + COMMAND_MAPPING[command_name]( + driver=driver, target=target, text=text, value=value, url=url + ) + else: + print(f"Command {command_name} not supported") + + def cookies_commands(self, driver, url, text="", value="", target=""): """ Execute custom commands to handle cookies acceptance on website. @@ -138,9 +173,9 @@ def cookies_commands(self, driver, url): ) as side_file: side_data = json.load(side_file) self.test_data["commands"] = side_data.get("commands", []) - self.run(driver, url) + self.run_commands(driver, url) - def open(self, driver, target): + def open(self, driver, target, text="", value="", url=""): """ Open a URL in the WebDriver. @@ -148,9 +183,9 @@ def open(self, driver, target): driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The URL to open. """ - driver.get(self.base_url + target) + driver.get(self.base_url + "/" + target) - def click(self, driver, target): + def click(self, driver, target, text="", value="", url=""): """ Click on a web element identified by a target. @@ -159,10 +194,13 @@ def click(self, driver, target): target (str): The identifier for the web element to be clicked. """ element = find_element(driver, target) - driver.execute_script("arguments[0].scrollIntoView();", element) + element_type = element.get_attribute("type") + without_scroll_elements = {"button", "submit", "reset", "radio", "checkbox"} + if element_type and element_type.lower() not in without_scroll_elements: + driver.execute_script("arguments[0].scrollIntoView();", element) element.click() - def clickAndWait(self, driver, target): + def clickAndWait(self, driver, target, text="", value="", url=""): """ Click on a web element identified by a target and wait for a response. @@ -172,21 +210,21 @@ def clickAndWait(self, driver, target): """ self.click(driver, target) - def type(self, driver, target, text=""): + def type(self, driver, target, text="", value="", url=""): """ Simulate typing text into an input field on a web page. Args: driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the input field. - text (str): The text to be typed into the input field (optional). + text (str, optional): The text to be typed into the input field. """ element = find_element(driver, target) element.click() element.clear() element.send_keys(text) - def select(self, driver, target, value): + def select(self, driver, target, value, text="", url=""): """ Select an option from a dropdown list on a web page. @@ -202,7 +240,7 @@ def select(self, driver, target, value): msg = "Don't know how to select %s on %s" raise Exception(msg % (value, target)) - def verifyTextPresent(self, driver, text): + def verifyTextPresent(self, driver, text, target="", value="", url=""): """ Verify if the specified text is present in the page source. @@ -217,7 +255,7 @@ def verifyTextPresent(self, driver, text): print("verifyTextPresent: ", repr(text), "not present in", repr(source)) raise - def verifyTextNotPresent(self, driver, text): + def verifyTextNotPresent(self, driver, text, target="", value="", url=""): """ Verify if the specified text is not present in the page source. @@ -231,7 +269,7 @@ def verifyTextNotPresent(self, driver, text): print("verifyNotTextPresent: ", repr(text), "present") raise - def assertElementPresent(self, driver, target): + def assertElementPresent(self, driver, target, text="", value="", url=""): """ Assert the presence of a specified web element. @@ -245,7 +283,7 @@ def assertElementPresent(self, driver, target): print("assertElementPresent: ", repr(target), "not present") raise - def verifyElementPresent(self, driver, target): + def verifyElementPresent(self, driver, target, text="", value="", url=""): """ Verify the presence of a specified web element on a web page. @@ -259,7 +297,7 @@ def verifyElementPresent(self, driver, target): print("verifyElementPresent: ", repr(target), "not present") raise - def verifyElementNotPresent(self, driver, target): + def verifyElementNotPresent(self, driver, target, text="", value="", url=""): """ Verify the absence of a specified web element on a web page. @@ -267,7 +305,6 @@ def verifyElementNotPresent(self, driver, target): driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element to be verified for absence. """ - present = True try: find_element(driver, target) @@ -280,7 +317,7 @@ def verifyElementNotPresent(self, driver, target): print("verifyElementNotPresent: ", repr(target), "present") raise - def waitForTextPresent(self, driver, text): + def waitForTextPresent(self, driver, text, target="", value="", url=""): """ Wait for the specified text to be present in the page source. @@ -288,14 +325,13 @@ def waitForTextPresent(self, driver, text): driver (WebDriver): The WebDriver instance for interacting with the web page. text (str): The text to wait for in the page source. """ - try: assert bool(text in driver.page_source) except Exception: print("waitForTextPresent: ", repr(text), "not present") raise - def waitForTextNotPresent(self, driver, text): + def waitForTextNotPresent(self, driver, text, target="", value="", url=""): """ Wait for the specified text to be absent in the page source. @@ -303,14 +339,13 @@ def waitForTextNotPresent(self, driver, text): driver (WebDriver): The WebDriver instance for interacting with the web page. text (str): The text to wait for to be absent in the page source. """ - try: assert not bool(text in driver.page_source) except Exception: print("waitForTextNotPresent: ", repr(text), "present") raise - def assertText(self, driver, target, value=""): + def assertText(self, driver, target, value="", text="", url=""): """ Assert that the text of a specified web element matches the expected value. @@ -319,7 +354,6 @@ def assertText(self, driver, target, value=""): target (str): The identifier for the web element whose text needs to be asserted. value (str, optional): The expected text value to compare against (default is an empty string). """ - try: target_value = find_element(driver, target).text print(" assertText target value =" + repr(target_value)) @@ -336,7 +370,7 @@ def assertText(self, driver, target, value=""): ) raise - def assertNotText(self, driver, target, value=""): + def assertNotText(self, driver, target, value="", text="", url=""): """ Assert that the text of a specified web element does not match the expected value. @@ -345,7 +379,6 @@ def assertNotText(self, driver, target, value=""): target (str): The identifier for the web element whose text needs to be asserted. value (str, optional): The expected text value to compare against (default is an empty string). """ - try: target_value = find_element(driver, target).text print(" assertNotText target value =" + repr(target_value)) @@ -362,7 +395,7 @@ def assertNotText(self, driver, target, value=""): ) raise - def assertValue(self, driver, target, value=""): + def assertValue(self, driver, target, value="", text="", url=""): """ Assert that the value attribute of a specified web element matches the expected value. @@ -371,7 +404,6 @@ def assertValue(self, driver, target, value=""): target (str): The identifier for the web element whose value attribute needs to be asserted. value (str, optional): The expected value to compare against (default is an empty string). """ - try: target_value = find_element(driver, target).get_attribute("value") print(" assertValue target value =" + repr(target_value)) @@ -385,7 +417,7 @@ def assertValue(self, driver, target, value=""): ) raise - def assertNotValue(self, driver, target, value=""): + def assertNotValue(self, driver, target, value="", text="", url=""): """ Assert that the value attribute of a specified web element does not match the expected value. @@ -394,7 +426,6 @@ def assertNotValue(self, driver, target, value=""): target (str): The identifier for the web element whose value attribute needs to be asserted. value (str, optional): The expected value to compare against (default is an empty string). """ - try: target_value = find_element(driver, target).get_attribute("value") print(" assertNotValue target value =" + repr(target_value)) @@ -408,7 +439,7 @@ def assertNotValue(self, driver, target, value=""): ) raise - def verifyValue(self, driver, target, value=""): + def verifyValue(self, driver, target, value="", text="", url=""): """ Verify that the value attribute of a specified web element matches the expected value. @@ -417,7 +448,6 @@ def verifyValue(self, driver, target, value=""): target (str): The identifier for the web element whose value attribute needs to be verified. value (str, optional): The expected value to compare against (default is an empty string). """ - try: target_value = find_element(driver, target).get_attribute("value") print(" verifyValue target value =" + repr(target_value)) @@ -431,6 +461,44 @@ def verifyValue(self, driver, target, value=""): ) raise + def mouseOver(self, driver, target, text="", value="", url=""): + """ + Simulate a mouse over event on the specified target element. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be verified. + """ + element = find_element(driver, target) + driver.execute_script( + "arguments[0].dispatchEvent(new Event('mouseover'))", element + ) + + def mouseOut(self, driver, target, text="", value="", url=""): + """ + Simulate a mouse out event on the specified target element. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be verified. + """ + element = find_element(driver, target) + driver.execute_script( + "arguments[0].dispatchEvent(new Event('mouseout'))", element + ) + + def sendKeys(self, driver, target, text="", value="", url=""): + """ + Send the specified text to the target element. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be verified. + text (str, optional): The text to send to the target element. + """ + element = find_element(driver, target) + element.send_keys(text) + class SeleniumTestSuite(object): """A Selenium Test Suite""" @@ -486,7 +554,7 @@ def __repr__(self): class PodSeleniumTests(StaticLiveServerTestCase): """Tests the integration of Pod application with Selenium from side files""" - fixtures = ["initial_data.json"] + fixtures = ["initial_data.json", "sample_videos.json"] def setUp(self): """Set up the tests and initialize custom test data.""" @@ -520,6 +588,7 @@ def initialize_data(self): is_draft=False, type=Type.objects.get(id=1), ) + encode_video(self.video.id) def run_suite(self, suite_name, suite_url): """ @@ -539,11 +608,11 @@ def run_suite(self, suite_name, suite_url): test_case = SeleniumTestCase(test_data) try: test_case.run(PodSeleniumTests.selenium, suite_url) - except Exception as e: + except Exception: PodSeleniumTests.selenium.save_screenshot( f"{suite_name}-error_screen.png" ) - print(f"Error in suite {suite_name}: {e}") + self.fail(f"Error in suite {suite_name}") def run_all_tests(self): """ @@ -583,13 +652,9 @@ def run_single_suite(self, suite_name): Args: suite_name (str): The name of the test suite JSON file to run. """ - with Display(visible=0, size=(1920, 1080)): - with WebDriver() as driver: - PodSeleniumTests.selenium = driver - PodSeleniumTests.setUpClass() - try: - suite_url = "http://localhost:8080" - self.run_suite(suite_name, suite_url) - except Exception as e: - print(f"Error in suite {suite_name}: {e}") - PodSeleniumTests.tearDownClass() + try: + suite_url = "http://localhost:8080" + self.run_suite(suite_name, suite_url) + except Exception: + PodSeleniumTests.selenium.save_screenshot(f"{suite_name}-error_screen.png") + self.fail(f"Error in suite {suite_name}") diff --git a/pod/main/integration_tests/tests/lang.side b/pod/main/integration_tests/tests/lang.side index af6a54315b..7f0cfc7054 100644 --- a/pod/main/integration_tests/tests/lang.side +++ b/pod/main/integration_tests/tests/lang.side @@ -16,11 +16,6 @@ "targets": [], "value": "" }, - { - "id": "pod-accept-cookies", - "comment": "Valide les cookies du navigateur", - "command": "cookies_commands" - }, { "id": "pod-open-configuration", "comment": "Ouvre la page de configuration", diff --git a/pod/main/integration_tests/tests/search.side b/pod/main/integration_tests/tests/search.side index 0282ddfa35..ede54b955d 100644 --- a/pod/main/integration_tests/tests/search.side +++ b/pod/main/integration_tests/tests/search.side @@ -16,11 +16,6 @@ "targets": [], "value": "" }, - { - "id": "pod-accept-cookies", - "comment": "Valide les cookies du navigateur", - "command": "cookies_commands" - }, { "id": "pod-search", "comment": "Sélectionne barre recherche", diff --git a/pod/video/integration_tests/tests/comment.side b/pod/video/integration_tests/tests/comment.side index 2e4c09c5f9..840be28b7e 100644 --- a/pod/video/integration_tests/tests/comment.side +++ b/pod/video/integration_tests/tests/comment.side @@ -8,16 +8,11 @@ "id": "pod-add-delete-comment", "name": "comment", "commands": [ - { - "id": "pod-accept-cookies", - "comment": "Valide les cookies du navigateur", - "command": "cookies_commands" - }, { "id": "pod-open-video-page", "comment": "Ouvre la page de la vidéo", "command": "open", - "target": "video/1-first-video/", + "target": "video/0001-first-video/", "targets": [], "value": "" }, diff --git a/requirements-dev.txt b/requirements-dev.txt index a410922b1c..93351c71c1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,4 +6,3 @@ httmock beautifulsoup4 django-debug-toolbar selenium -pyvirtualdisplay From 87aadde7c6a459c28dbdf9c6c0c5b4010d57105b Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Thu, 12 Oct 2023 12:03:17 +0200 Subject: [PATCH 43/52] Test tearDownClass --- pod/main/integration_tests/selenium_pod_integration_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 81ecc7f390..685e7f86c7 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -554,7 +554,7 @@ def __repr__(self): class PodSeleniumTests(StaticLiveServerTestCase): """Tests the integration of Pod application with Selenium from side files""" - fixtures = ["initial_data.json", "sample_videos.json"] + fixtures = ["initial_data.json"] def setUp(self): """Set up the tests and initialize custom test data.""" @@ -576,7 +576,9 @@ def tearDownClass(cls): @override_settings(DEBUG=False) def test_selenium_suites(self): """Run Selenium Test Suites from Side files in all installed apps.""" + self.setUpClass() self.run_all_tests() + self.tearDownClass() def initialize_data(self): """Initialize custom test data.""" From 65487d560c93f0c246a1165950676122a9e95050 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Thu, 12 Oct 2023 12:59:37 +0200 Subject: [PATCH 44/52] Change suite_url --- pod/main/integration_tests/selenium_pod_integration_tests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 685e7f86c7..950a82b0b5 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -576,9 +576,8 @@ def tearDownClass(cls): @override_settings(DEBUG=False) def test_selenium_suites(self): """Run Selenium Test Suites from Side files in all installed apps.""" - self.setUpClass() + self.selenium.get(f"{self.live_server_url}/") self.run_all_tests() - self.tearDownClass() def initialize_data(self): """Initialize custom test data.""" @@ -655,7 +654,7 @@ def run_single_suite(self, suite_name): suite_name (str): The name of the test suite JSON file to run. """ try: - suite_url = "http://localhost:8080" + suite_url = self.live_server_url self.run_suite(suite_name, suite_url) except Exception: PodSeleniumTests.selenium.save_screenshot(f"{suite_name}-error_screen.png") From ff0a6a305092bad0ef1ee025b5cddc6ef91575b5 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Thu, 12 Oct 2023 16:38:15 +0200 Subject: [PATCH 45/52] Add connexion test and improve code --- .../selenium_pod_integration_tests.py | 69 +++++----- .../integration_tests/tests/connexion.side | 122 ++++++++++++++++++ 2 files changed, 161 insertions(+), 30 deletions(-) create mode 100644 pod/main/integration_tests/tests/connexion.side diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 950a82b0b5..7a337d9ba2 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -78,8 +78,7 @@ def find_element(driver, target): except NoSuchElementException: result = driver.find_element(locator, value.lower()) target_cache[target] = f"{locator}={value.lower()}" - msg = f"label {value} is being cached as {target_cache[target]}" - print(msg) + print(f"label {value} is being cached as {target_cache[target]}") return result else: direct = ( @@ -145,22 +144,22 @@ def run_commands(self, driver, url): "mouseOver": self.mouseOver, "mouseOut": self.mouseOut, "sendKeys": self.sendKeys, + "executeScript": self.executeScript, } for command in self.test_data.get("commands", []): command_name = command.get("command", "") target = command.get("target", "") value = command.get("value", "") - text = command.get("text", "") if command_name in COMMAND_MAPPING: COMMAND_MAPPING[command_name]( - driver=driver, target=target, text=text, value=value, url=url + driver=driver, target=target, value=value, url=url ) else: print(f"Command {command_name} not supported") - def cookies_commands(self, driver, url, text="", value="", target=""): + def cookies_commands(self, driver, url, value="", target=""): """ Execute custom commands to handle cookies acceptance on website. @@ -175,7 +174,7 @@ def cookies_commands(self, driver, url, text="", value="", target=""): self.test_data["commands"] = side_data.get("commands", []) self.run_commands(driver, url) - def open(self, driver, target, text="", value="", url=""): + def open(self, driver, target, value="", url=""): """ Open a URL in the WebDriver. @@ -185,7 +184,7 @@ def open(self, driver, target, text="", value="", url=""): """ driver.get(self.base_url + "/" + target) - def click(self, driver, target, text="", value="", url=""): + def click(self, driver, target, value="", url=""): """ Click on a web element identified by a target. @@ -194,13 +193,13 @@ def click(self, driver, target, text="", value="", url=""): target (str): The identifier for the web element to be clicked. """ element = find_element(driver, target) - element_type = element.get_attribute("type") - without_scroll_elements = {"button", "submit", "reset", "radio", "checkbox"} - if element_type and element_type.lower() not in without_scroll_elements: + element_tag = element.tag_name + without_scroll_elements = {"button", "submit", "textarea", "input"} + if element_tag not in without_scroll_elements: driver.execute_script("arguments[0].scrollIntoView();", element) - element.click() + driver.execute_script("arguments[0].click();", element) - def clickAndWait(self, driver, target, text="", value="", url=""): + def clickAndWait(self, driver, target, value="", url=""): """ Click on a web element identified by a target and wait for a response. @@ -210,21 +209,21 @@ def clickAndWait(self, driver, target, text="", value="", url=""): """ self.click(driver, target) - def type(self, driver, target, text="", value="", url=""): + def type(self, driver, target, value="", url=""): """ Simulate typing text into an input field on a web page. Args: driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the input field. - text (str, optional): The text to be typed into the input field. + value (str, optional): The value to be typed into the input field. """ element = find_element(driver, target) element.click() element.clear() - element.send_keys(text) + element.send_keys(value) - def select(self, driver, target, value, text="", url=""): + def select(self, driver, target, value, url=""): """ Select an option from a dropdown list on a web page. @@ -269,7 +268,7 @@ def verifyTextNotPresent(self, driver, text, target="", value="", url=""): print("verifyNotTextPresent: ", repr(text), "present") raise - def assertElementPresent(self, driver, target, text="", value="", url=""): + def assertElementPresent(self, driver, target, value="", url=""): """ Assert the presence of a specified web element. @@ -283,7 +282,7 @@ def assertElementPresent(self, driver, target, text="", value="", url=""): print("assertElementPresent: ", repr(target), "not present") raise - def verifyElementPresent(self, driver, target, text="", value="", url=""): + def verifyElementPresent(self, driver, target, value="", url=""): """ Verify the presence of a specified web element on a web page. @@ -297,7 +296,7 @@ def verifyElementPresent(self, driver, target, text="", value="", url=""): print("verifyElementPresent: ", repr(target), "not present") raise - def verifyElementNotPresent(self, driver, target, text="", value="", url=""): + def verifyElementNotPresent(self, driver, target, value="", url=""): """ Verify the absence of a specified web element on a web page. @@ -345,7 +344,7 @@ def waitForTextNotPresent(self, driver, text, target="", value="", url=""): print("waitForTextNotPresent: ", repr(text), "present") raise - def assertText(self, driver, target, value="", text="", url=""): + def assertText(self, driver, target, value="", url=""): """ Assert that the text of a specified web element matches the expected value. @@ -370,7 +369,7 @@ def assertText(self, driver, target, value="", text="", url=""): ) raise - def assertNotText(self, driver, target, value="", text="", url=""): + def assertNotText(self, driver, target, value="", url=""): """ Assert that the text of a specified web element does not match the expected value. @@ -395,7 +394,7 @@ def assertNotText(self, driver, target, value="", text="", url=""): ) raise - def assertValue(self, driver, target, value="", text="", url=""): + def assertValue(self, driver, target, value="", url=""): """ Assert that the value attribute of a specified web element matches the expected value. @@ -417,7 +416,7 @@ def assertValue(self, driver, target, value="", text="", url=""): ) raise - def assertNotValue(self, driver, target, value="", text="", url=""): + def assertNotValue(self, driver, target, value="", url=""): """ Assert that the value attribute of a specified web element does not match the expected value. @@ -439,7 +438,7 @@ def assertNotValue(self, driver, target, value="", text="", url=""): ) raise - def verifyValue(self, driver, target, value="", text="", url=""): + def verifyValue(self, driver, target, value="", url=""): """ Verify that the value attribute of a specified web element matches the expected value. @@ -461,7 +460,7 @@ def verifyValue(self, driver, target, value="", text="", url=""): ) raise - def mouseOver(self, driver, target, text="", value="", url=""): + def mouseOver(self, driver, target, value="", url=""): """ Simulate a mouse over event on the specified target element. @@ -474,7 +473,7 @@ def mouseOver(self, driver, target, text="", value="", url=""): "arguments[0].dispatchEvent(new Event('mouseover'))", element ) - def mouseOut(self, driver, target, text="", value="", url=""): + def mouseOut(self, driver, target, value="", url=""): """ Simulate a mouse out event on the specified target element. @@ -487,17 +486,27 @@ def mouseOut(self, driver, target, text="", value="", url=""): "arguments[0].dispatchEvent(new Event('mouseout'))", element ) - def sendKeys(self, driver, target, text="", value="", url=""): + def sendKeys(self, driver, target, value="", url=""): """ Send the specified text to the target element. Args: driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The identifier for the web element whose value attribute needs to be verified. - text (str, optional): The text to send to the target element. + value (str, optional): The text to send to the target element. """ element = find_element(driver, target) - element.send_keys(text) + element.send_keys(value) + + def executeScript(self, driver, target, value="", url=""): + """ + Execute a script script in the browser. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + script (str): The script code to execute. + """ + driver.execute_script(target) class SeleniumTestSuite(object): @@ -581,7 +590,7 @@ def test_selenium_suites(self): def initialize_data(self): """Initialize custom test data.""" - self.user = User.objects.create_user(username="testuser", password="testpassword") + self.user = User.objects.create_user(username="user", password="user") self.video = Video.objects.create( title="first-video", owner=self.user, diff --git a/pod/main/integration_tests/tests/connexion.side b/pod/main/integration_tests/tests/connexion.side new file mode 100644 index 0000000000..0431f4bad2 --- /dev/null +++ b/pod/main/integration_tests/tests/connexion.side @@ -0,0 +1,122 @@ +{ + "id": "5ce4dc16-363c-4d63-8b80-7eb881c4720c", + "version": "2.0", + "name": "pod-front", + "url": "http://localhost:9090/", + "tests": [{ + "id": "pod-connexion", + "name": "connexion", + "commands": [{ + "id": "pod-open-home", + "comment": "Ouvre la page d'accueil", + "command": "open", + "target": "http://localhost:9090/", + "targets": [], + "value": "" + }, { + "id": "pod-click-connection-button", + "comment": "Clique sur bouton de connexion", + "command": "click", + "target": "xpath=//li[@id='nav-authentication']/a/i", + "targets": [ + ["css=.bi-person-circle", "css:finder"], + ["xpath=//li[@id='nav-authentication']/a/i", "xpath:idRelative"], + ["xpath=//nav/div/ul/li[2]/a/i", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-select-id", + "comment": "Sélectionne le formulaire d'identifiant", + "command": "click", + "target": "id=id_username", + "targets": [ + ["id=id_username", "id"], + ["name=username", "name"], + ["css=#id_username", "css:finder"], + ["xpath=//input[@id='id_username']", "xpath:attributes"], + ["xpath=//form[@id='login-form']/div/input", "xpath:idRelative"], + ["xpath=//div/div/form/div/input", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-type-id", + "comment": "Ecrit l'identifiant", + "command": "type", + "target": "id=id_username", + "targets": [ + ["id=id_username", "id"], + ["name=username", "name"], + ["css=#id_username", "css:finder"], + ["xpath=//input[@id='id_username']", "xpath:attributes"], + ["xpath=//form[@id='login-form']/div/input", "xpath:idRelative"], + ["xpath=//div/div/form/div/input", "xpath:position"] + ], + "value": "user" + }, { + "id": "pod-select-password", + "comment": "Sélectionne le formulaire de mot de passe", + "command": "click", + "target": "id=id_password", + "targets": [ + ["id=id_password", "id"], + ["name=password", "name"], + ["css=#id_password", "css:finder"], + ["xpath=//input[@id='id_password']", "xpath:attributes"], + ["xpath=//form[@id='login-form']/div[2]/input", "xpath:idRelative"], + ["xpath=//div[2]/input", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-type-password", + "comment": "Ecrit le mot de passe", + "command": "type", + "target": "id=id_password", + "targets": [ + ["id=id_password", "id"], + ["name=password", "name"], + ["css=#id_password", "css:finder"], + ["xpath=//input[@id='id_password']", "xpath:attributes"], + ["xpath=//form[@id='login-form']/div[2]/input", "xpath:idRelative"], + ["xpath=//div[2]/input", "xpath:position"] + ], + "value": "user" + }, { + "id": "pod-send-login-form", + "comment": "Clique sur bouton de connexion", + "command": "click", + "target": "xpath=//form[@id='login-form']/button", + "targets": [ + ["css=.btn:nth-child(4)", "css:finder"], + ["xpath=(//button[@type='submit'])[2]", "xpath:attributes"], + ["xpath=//form[@id='login-form']/button", "xpath:idRelative"], + ["xpath=//form/button", "xpath:position"], + ["xpath=//button[contains(.,'Connexion')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "pod-get-url", + "comment": "Récupère et stocke dans la variable podURL la valeur de l'url", + "command": "executeScript", + "target": "return document.URL;", + "targets": [], + "value": "podURL" + }, { + "id": "pod-check-url", + "comment": "Vérifie qu'il y a bien redirection vers page d'accueil et donc connexion", + "command": "assert", + "target": "podURL", + "targets": [], + "value": "http://localhost:9090/" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-connexion"] + }], + "urls": ["http://localhost:9090/"], + "plugins": [] +} From 441e9b38d76bcd0761924efbb59ae23cbae36e96 Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Fri, 13 Oct 2023 11:28:33 +0200 Subject: [PATCH 46/52] Add order for tests --- .../connexion.side | 0 .../{tests => init_integration_tests}/cookies.side | 0 .../selenium_pod_integration_tests.py | 14 ++++++++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) rename pod/main/integration_tests/{tests => init_integration_tests}/connexion.side (100%) rename pod/main/integration_tests/{tests => init_integration_tests}/cookies.side (100%) diff --git a/pod/main/integration_tests/tests/connexion.side b/pod/main/integration_tests/init_integration_tests/connexion.side similarity index 100% rename from pod/main/integration_tests/tests/connexion.side rename to pod/main/integration_tests/init_integration_tests/connexion.side diff --git a/pod/main/integration_tests/tests/cookies.side b/pod/main/integration_tests/init_integration_tests/cookies.side similarity index 100% rename from pod/main/integration_tests/tests/cookies.side rename to pod/main/integration_tests/init_integration_tests/cookies.side diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 7a337d9ba2..b679cb8208 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -197,7 +197,7 @@ def click(self, driver, target, value="", url=""): without_scroll_elements = {"button", "submit", "textarea", "input"} if element_tag not in without_scroll_elements: driver.execute_script("arguments[0].scrollIntoView();", element) - driver.execute_script("arguments[0].click();", element) + element.click() def clickAndWait(self, driver, target, value="", url=""): """ @@ -586,6 +586,7 @@ def tearDownClass(cls): def test_selenium_suites(self): """Run Selenium Test Suites from Side files in all installed apps.""" self.selenium.get(f"{self.live_server_url}/") + self.run_initial_tests() self.run_all_tests() def initialize_data(self): @@ -594,7 +595,7 @@ def initialize_data(self): self.video = Video.objects.create( title="first-video", owner=self.user, - video="video.mp4", + video="pod/main/static/video_test/pod.mp4", is_draft=False, type=Type.objects.get(id=1), ) @@ -624,6 +625,15 @@ def run_suite(self, suite_name, suite_url): ) self.fail(f"Error in suite {suite_name}") + def run_initial_tests(self): + """Run initial Selenium test suites for cookies and login.""" + initial_tests_dir = os.path.join( + os.path.dirname(__file__), "init_integration_tests") + cookies_test_path = os.path.join(initial_tests_dir, "cookies.side") + login_test_path = os.path.join(initial_tests_dir, "connexion.side") + self.run_single_suite(cookies_test_path) + self.run_single_suite(login_test_path) + def run_all_tests(self): """ Run Selenium test suites in all installed apps. From 7d889d9b9cdcc741a7bb728b36bf1b3bb4cf8dad Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Thu, 2 Nov 2023 11:22:04 +0100 Subject: [PATCH 47/52] Add pause function --- .../selenium_pod_integration_tests.py | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index b679cb8208..492a745e87 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -21,6 +21,7 @@ target_cache = {} +current_side_file = None def parse_target(target): @@ -125,6 +126,7 @@ def run_commands(self, driver, url): COMMAND_MAPPING = { "open": self.open, "click": self.click, + "pause": self.pause, "cookies_commands": self.cookies_commands, "clickAndWait": self.clickAndWait, "type": self.type, @@ -194,10 +196,30 @@ def click(self, driver, target, value="", url=""): """ element = find_element(driver, target) element_tag = element.tag_name - without_scroll_elements = {"button", "submit", "textarea", "input"} + without_scroll_elements = { + "button", + "submit", + "textarea", + "input", + "div", + "span", + "i", + } if element_tag not in without_scroll_elements: driver.execute_script("arguments[0].scrollIntoView();", element) - element.click() + element.click() + else: + driver.execute_script("arguments[0].click();", element) + + def pause(self, driver, target, value="", url=""): + """ + Make a pause during target value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The during value to wait. + """ + driver.implicitly_wait(target) def clickAndWait(self, driver, target, value="", url=""): """ @@ -609,6 +631,7 @@ def run_suite(self, suite_name, suite_url): suite_name (str): The name of the test suite JSON file. suite_url (str): The base URL for the test suite. """ + global current_side_file print(f"Running test suite: {suite_name}") with open(suite_name, "r") as json_file: @@ -623,6 +646,7 @@ def run_suite(self, suite_name, suite_url): PodSeleniumTests.selenium.save_screenshot( f"{suite_name}-error_screen.png" ) + current_side_file = suite_name self.fail(f"Error in suite {suite_name}") def run_initial_tests(self): @@ -677,4 +701,6 @@ def run_single_suite(self, suite_name): self.run_suite(suite_name, suite_url) except Exception: PodSeleniumTests.selenium.save_screenshot(f"{suite_name}-error_screen.png") + print("ERROR !") + print(f"Side path : {current_side_file}") self.fail(f"Error in suite {suite_name}") From d4b1311600f27da66a560dae421c48840abbce6c Mon Sep 17 00:00:00 2001 From: Aymeric Jakobowski Date: Thu, 2 Nov 2023 11:52:43 +0100 Subject: [PATCH 48/52] Improve gitignore --- .gitignore | 1 + pod/main/integration_tests/selenium_pod_integration_tests.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 28bf70b800..c76f6a35e7 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ transcription/* # Unit test utilities # ####################### chromedriver +**/integration_tests/**/*.png ## Others pod/static/ diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 492a745e87..80e12bace6 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -18,6 +18,7 @@ from selenium.webdriver.support.ui import Select from pod.video.models import Video, Type +import time target_cache = {} @@ -205,6 +206,7 @@ def click(self, driver, target, value="", url=""): "span", "i", } + time.sleep(1) if element_tag not in without_scroll_elements: driver.execute_script("arguments[0].scrollIntoView();", element) element.click() From 8cdddd55197bf804ba5422ede2ada5c22eb1ab85 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Tue, 7 Nov 2023 11:24:31 +0100 Subject: [PATCH 49/52] fix click and clear for simulate typing text function --- .../selenium_pod_integration_tests.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 80e12bace6..ccdfac19a3 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -16,6 +16,7 @@ from selenium.webdriver.common.by import By from selenium.webdriver.firefox.webdriver import WebDriver from selenium.webdriver.support.ui import Select +from selenium.webdriver.common.keys import Keys from pod.video.models import Video, Type import time @@ -243,8 +244,25 @@ def type(self, driver, target, value="", url=""): value (str, optional): The value to be typed into the input field. """ element = find_element(driver, target) - element.click() - element.clear() + element_tag = element.tag_name + without_scroll_elements = { + "button", + "submit", + "textarea", + "input", + "div", + "span", + "i", + } + time.sleep(1) + if element_tag not in without_scroll_elements: + driver.execute_script("arguments[0].scrollIntoView();", element) + element.click() + element.clear() + else: + driver.execute_script("arguments[0].click();", element) + element.send_keys(Keys.CONTROL, 'a') + element.send_keys(Keys.DELETE) element.send_keys(value) def select(self, driver, target, value, url=""): From a8c449452665ba70e08460c4d20c647c2a823301 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Tue, 7 Nov 2023 15:21:43 +0100 Subject: [PATCH 50/52] add side for subtitles and fix load web vtt from old caption --- .../integration_tests/tests/subtitles.side | 170 ++++++++++++++++++ pod/completion/static/js/caption_maker.js | 1 + 2 files changed, 171 insertions(+) create mode 100644 pod/completion/integration_tests/tests/subtitles.side diff --git a/pod/completion/integration_tests/tests/subtitles.side b/pod/completion/integration_tests/tests/subtitles.side new file mode 100644 index 0000000000..00cd4713ac --- /dev/null +++ b/pod/completion/integration_tests/tests/subtitles.side @@ -0,0 +1,170 @@ +{ + "id": "9ea49e3a-2f6e-49aa-b1c7-975e8c0f70d3", + "version": "2.0", + "name": "pod-front", + "url": "http://localhost:9090/", + "tests": [{ + "id": "pod-sous-titres-mode-edition", + "name": "Untitled", + "commands": [{ + "id": "bb668b94-3724-482f-b0e9-ba055f0e3000", + "comment": "Ouvre la page de complétion", + "command": "open", + "target": "/video/completion/0001-first-video/", + "targets": [], + "value": "" + }, { + "id": "833e6f0f-cfb0-494d-b62f-4dd944b3df42", + "comment": "Ouvre l'accordéon des sous-titres", + "command": "click", + "target": "id=section_track", + "targets": [ + ["id=section_track", "id"], + ["linkText=Sous-titres et légendes", "linkText"], + ["css=#section_track", "css:finder"], + ["xpath=//a[contains(text(),'Sous-titres et légendes')]", "xpath:link"], + ["xpath=//a[@id='section_track']", "xpath:attributes"], + ["xpath=//div[@id='accordeon']/li[3]/a", "xpath:idRelative"], + ["xpath=(//a[contains(@href, '#')])[3]", "xpath:href"], + ["xpath=//div/li[3]/a", "xpath:position"], + ["xpath=//a[contains(.,'Sous-titres et légendes ')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "18f5e568-9ae1-4e43-920d-8e7aca81aa83", + "comment": "Clique sur le bouton pour ouvrir l'outil de création de sous-titres", + "command": "click", + "target": "linkText=Outil de création de fichier de sous-titres/légende", + "targets": [ + ["linkText=Outil de création de fichier de sous-titres/légende", "linkText"], + ["css=p > .btn", "css:finder"], + ["xpath=//a[contains(text(),'Outil de création de fichier de sous-titres/légende')]", "xpath:link"], + ["xpath=//div[@id='accordeon']/li[4]/div/p/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '/video/completion/caption_maker/33955-bbb/')]", "xpath:href"], + ["xpath=//p/a", "xpath:position"], + ["xpath=//a[contains(.,'Outil de création de fichier de sous-titres/légende')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "80aceab9-1cac-42ce-ba1e-e4e0ffb5fa27", + "comment": "Clique sur le bouton pour ajouter un sous-titre", + "command": "click", + "target": "id=addSubtitle", + "targets": [ + ["id=addSubtitle", "id"], + ["css=#addSubtitle", "css:finder"], + ["xpath=//button[@id='addSubtitle']", "xpath:attributes"], + ["xpath=//div[@id='newCaptionsEditor']/button", "xpath:idRelative"], + ["xpath=//div[2]/div[2]/button", "xpath:position"], + ["xpath=//button[contains(.,' Ajouter un(e) légende / sous-titre')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "042569e8-f390-40c1-a04f-413c88519950", + "comment": "Sélectionne le champ des sous-titres", + "command": "click", + "target": "name=captionTextInput", + "targets": [ + ["id=c40389510", "id"], + ["name=captionTextInput", "name"], + ["css=#c40389510", "css:finder"], + ["xpath=//textarea[@id='c40389510']", "xpath:attributes"], + ["xpath=//div[@id='newCaptionsEditor']/form/div[2]/textarea", "xpath:idRelative"], + ["xpath=//div[2]/textarea", "xpath:position"] + ], + "value": "" + }, { + "id": "3f6c5cb2-d467-4e35-9d55-3b69bba7dd13", + "comment": "Écrit le contenu du sous-titre", + "command": "type", + "target": "name=captionTextInput", + "targets": [ + ["id=c40389510", "id"], + ["name=captionTextInput", "name"], + ["css=#c40389510", "css:finder"], + ["xpath=//textarea[@id='c40389510']", "xpath:attributes"], + ["xpath=//div[@id='newCaptionsEditor']/form/div[2]/textarea", "xpath:idRelative"], + ["xpath=//div[2]/textarea", "xpath:position"] + ], + "value": "Texte initial" + }, { + "id": "ffed3160-9509-4730-8478-c0c6543419a2", + "comment": "Change le mode d'édition", + "command": "click", + "target": "id=switchOldEditMode", + "targets": [ + ["id=switchOldEditMode", "id"], + ["css=#switchOldEditMode", "css:finder"], + ["xpath=//button[@id='switchOldEditMode']", "xpath:attributes"], + ["xpath=//div[@id='pod-mainContent']/div[2]/div/div/div/div[7]/button", "xpath:idRelative"], + ["xpath=//div[7]/button", "xpath:position"], + ["xpath=//button[contains(.,' Changer le mode d’édition')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "50f96a38-c5bd-49bb-babe-47227a1a022e", + "comment": "Sélectionne le champ des sous-titres", + "command": "click", + "target": "id=captionContent", + "targets": [ + ["id=captionContent", "id"], + ["css=#captionContent", "css:finder"], + ["xpath=//textarea[@id='captionContent']", "xpath:attributes"], + ["xpath=//div[@id='rawCaptionsEditor']/textarea", "xpath:idRelative"], + ["xpath=//div[2]/div/textarea", "xpath:position"] + ], + "value": "" + }, { + "id": "0ee2ab41-f02e-4a7f-a865-4caf1500d8ed", + "comment": "Modifie le contenu du sous-titre", + "command": "type", + "target": "id=captionContent", + "targets": [ + ["id=captionContent", "id"], + ["css=#captionContent", "css:finder"], + ["xpath=//textarea[@id='captionContent']", "xpath:attributes"], + ["xpath=//div[@id='rawCaptionsEditor']/textarea", "xpath:idRelative"], + ["xpath=//div[2]/div/textarea", "xpath:position"] + ], + "value": "WEBVTT\\n\\n00:00.000 --> 00:02.000\\nTexte corrigé" + }, { + "id": "0bd7bfef-23b1-4eee-82aa-e8e10daf2541", + "comment": "Reviens au mode d'édition par défaut", + "command": "click", + "target": "id=switchOldEditMode", + "targets": [ + ["id=switchOldEditMode", "id"], + ["css=#switchOldEditMode", "css:finder"], + ["xpath=//button[@id='switchOldEditMode']", "xpath:attributes"], + ["xpath=//div[@id='pod-mainContent']/div[2]/div/div/div/div[7]/button", "xpath:idRelative"], + ["xpath=//div[7]/button", "xpath:position"], + ["xpath=//button[contains(.,' Changer le mode d’édition')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "bf33e57c-b117-4d43-ba0f-ce246565ed15", + "comment": "Récupère la valeur du champ du sous-titre", + "command": "storeValue", + "target": "name=captionTextInput", + "targets": [], + "value": "sousTitre" + }, { + "id": "ee3f5108-139c-4e56-8ecd-5f4fe90208ad", + "comment": "Vérifie si la valeur modifiée est bien prise en compte d'un mode d'édition à l'autre", + "command": "assert", + "target": "sousTitre", + "targets": [], + "value": "Texte corrigé" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-sous-titres-mode-edition"] + }], + "urls": ["http://localhost:9090/"], + "plugins": [] +} \ No newline at end of file diff --git a/pod/completion/static/js/caption_maker.js b/pod/completion/static/js/caption_maker.js index a1e101eb10..6e7dcd5077 100644 --- a/pod/completion/static/js/caption_maker.js +++ b/pod/completion/static/js/caption_maker.js @@ -286,6 +286,7 @@ document } } else { oldModeSelected = !oldModeSelected; + parseAndLoadWebVTT(document.getElementById("captionContent").value); document.getElementById("rawCaptionsEditor").style.display = "none"; document.getElementById("newCaptionsEditor").style.display = "block"; } From 3f579944210181f34e81156d72ad7a3f1dfe081f Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 9 Nov 2023 15:52:11 +0100 Subject: [PATCH 51/52] workon selenium side - add simple side and staff side. --- .../{subtitles.side => subtitles_staff.side} | 17 ++- .../init_integration_tests/connexion.side | 2 +- .../connexion_staff.side | 122 ++++++++++++++++ .../init_integration_tests/deconnexion.side | 42 ++++++ .../selenium_pod_integration_tests.py | 89 ++++++++++-- .../sides/pod-configuration.side | 131 ------------------ .../tests/{lang.side => lang_simple.side} | 0 .../tests/{search.side => search_simple.side} | 0 .../{comment.side => comment_simple.side} | 2 +- 9 files changed, 261 insertions(+), 144 deletions(-) rename pod/completion/integration_tests/tests/{subtitles.side => subtitles_staff.side} (88%) create mode 100644 pod/main/integration_tests/init_integration_tests/connexion_staff.side create mode 100644 pod/main/integration_tests/init_integration_tests/deconnexion.side delete mode 100644 pod/main/integration_tests/sides/pod-configuration.side rename pod/main/integration_tests/tests/{lang.side => lang_simple.side} (100%) rename pod/main/integration_tests/tests/{search.side => search_simple.side} (100%) rename pod/video/integration_tests/tests/{comment.side => comment_simple.side} (98%) diff --git a/pod/completion/integration_tests/tests/subtitles.side b/pod/completion/integration_tests/tests/subtitles_staff.side similarity index 88% rename from pod/completion/integration_tests/tests/subtitles.side rename to pod/completion/integration_tests/tests/subtitles_staff.side index 00cd4713ac..03d384d8a9 100644 --- a/pod/completion/integration_tests/tests/subtitles.side +++ b/pod/completion/integration_tests/tests/subtitles_staff.side @@ -10,7 +10,7 @@ "id": "bb668b94-3724-482f-b0e9-ba055f0e3000", "comment": "Ouvre la page de complétion", "command": "open", - "target": "/video/completion/0001-first-video/", + "target": "/video/completion/0002-video-staff/", "targets": [], "value": "" }, { @@ -30,6 +30,21 @@ ["xpath=//a[contains(.,'Sous-titres et légendes ')]", "xpath:innerText"] ], "value": "" + }, { + "id": "9292c6ab-76d7-43d6-9102-cf80e772acac", + "comment": "", + "command": "waitForElementVisible", + "target": "linkText=Outil de création de fichier de sous-titres/légende", + "targets": [ + ["linkText=Outil de création de fichier de sous-titres/légende", "linkText"], + ["css=p > .btn", "css:finder"], + ["xpath=//a[contains(text(),'Outil de création de fichier de sous-titres/légende')]", "xpath:link"], + ["xpath=//div[@id='accordeon']/li[4]/div/p/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '/video/completion/caption_maker/0002-video-staff/')]", "xpath:href"], + ["xpath=//p/a", "xpath:position"], + ["xpath=//a[contains(.,'Outil de création de fichier de sous-titres/légende')]", "xpath:innerText"] + ], + "value": "30000" }, { "id": "18f5e568-9ae1-4e43-920d-8e7aca81aa83", "comment": "Clique sur le bouton pour ouvrir l'outil de création de sous-titres", diff --git a/pod/main/integration_tests/init_integration_tests/connexion.side b/pod/main/integration_tests/init_integration_tests/connexion.side index 0431f4bad2..b3d91f8c9f 100644 --- a/pod/main/integration_tests/init_integration_tests/connexion.side +++ b/pod/main/integration_tests/init_integration_tests/connexion.side @@ -10,7 +10,7 @@ "id": "pod-open-home", "comment": "Ouvre la page d'accueil", "command": "open", - "target": "http://localhost:9090/", + "target": "/", "targets": [], "value": "" }, { diff --git a/pod/main/integration_tests/init_integration_tests/connexion_staff.side b/pod/main/integration_tests/init_integration_tests/connexion_staff.side new file mode 100644 index 0000000000..bafc1778b0 --- /dev/null +++ b/pod/main/integration_tests/init_integration_tests/connexion_staff.side @@ -0,0 +1,122 @@ +{ + "id": "5ce4dc16-363c-4d63-8b80-7eb881c4720c", + "version": "2.0", + "name": "pod-front", + "url": "http://localhost:9090/", + "tests": [{ + "id": "pod-connexion-staff", + "name": "connexion staff", + "commands": [{ + "id": "pod-open-home", + "comment": "accueil", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "pod-click-connection-button", + "comment": "Clique sur bouton de connexion", + "command": "click", + "target": "xpath=//li[@id='nav-authentication']/a/i", + "targets": [ + ["css=.bi-person-circle", "css:finder"], + ["xpath=//li[@id='nav-authentication']/a/i", "xpath:idRelative"], + ["xpath=//nav/div/ul/li[2]/a/i", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-select-id", + "comment": "Sélectionne le formulaire d'identifiant", + "command": "click", + "target": "id=id_username", + "targets": [ + ["id=id_username", "id"], + ["name=username", "name"], + ["css=#id_username", "css:finder"], + ["xpath=//input[@id='id_username']", "xpath:attributes"], + ["xpath=//form[@id='login-form']/div/input", "xpath:idRelative"], + ["xpath=//div/div/form/div/input", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-type-id", + "comment": "Ecrit l'identifiant", + "command": "type", + "target": "id=id_username", + "targets": [ + ["id=id_username", "id"], + ["name=username", "name"], + ["css=#id_username", "css:finder"], + ["xpath=//input[@id='id_username']", "xpath:attributes"], + ["xpath=//form[@id='login-form']/div/input", "xpath:idRelative"], + ["xpath=//div/div/form/div/input", "xpath:position"] + ], + "value": "staff_user" + }, { + "id": "pod-select-password", + "comment": "Sélectionne le formulaire de mot de passe", + "command": "click", + "target": "id=id_password", + "targets": [ + ["id=id_password", "id"], + ["name=password", "name"], + ["css=#id_password", "css:finder"], + ["xpath=//input[@id='id_password']", "xpath:attributes"], + ["xpath=//form[@id='login-form']/div[2]/input", "xpath:idRelative"], + ["xpath=//div[2]/input", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-type-password", + "comment": "Ecrit le mot de passe", + "command": "type", + "target": "id=id_password", + "targets": [ + ["id=id_password", "id"], + ["name=password", "name"], + ["css=#id_password", "css:finder"], + ["xpath=//input[@id='id_password']", "xpath:attributes"], + ["xpath=//form[@id='login-form']/div[2]/input", "xpath:idRelative"], + ["xpath=//div[2]/input", "xpath:position"] + ], + "value": "user" + }, { + "id": "pod-send-login-form", + "comment": "Clique sur bouton de connexion", + "command": "click", + "target": "xpath=//form[@id='login-form']/button", + "targets": [ + ["css=.btn:nth-child(4)", "css:finder"], + ["xpath=(//button[@type='submit'])[2]", "xpath:attributes"], + ["xpath=//form[@id='login-form']/button", "xpath:idRelative"], + ["xpath=//form/button", "xpath:position"], + ["xpath=//button[contains(.,'Connexion')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "pod-get-url", + "comment": "Récupère et stocke dans la variable podURL la valeur de l'url", + "command": "executeScript", + "target": "return document.URL;", + "targets": [], + "value": "podURL" + }, { + "id": "pod-check-url", + "comment": "Vérifie qu'il y a bien redirection vers page d'accueil et donc connexion", + "command": "assert", + "target": "podURL", + "targets": [], + "value": "http://localhost:9090/" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-connexion-staff"] + }], + "urls": ["http://localhost:9090/"], + "plugins": [] +} diff --git a/pod/main/integration_tests/init_integration_tests/deconnexion.side b/pod/main/integration_tests/init_integration_tests/deconnexion.side new file mode 100644 index 0000000000..02cc6a1d35 --- /dev/null +++ b/pod/main/integration_tests/init_integration_tests/deconnexion.side @@ -0,0 +1,42 @@ +{ + "id": "5ce4dc16-363c-4d63-8b80-7eb881c4720c", + "version": "2.0", + "name": "pod-front", + "url": "http://localhost:9090/", + "tests": [{ + "id": "pod-deconnexion", + "name": "deconnexion", + "commands": [{ + "id": "pod-open-deconnexion", + "comment": "accueil", + "command": "open", + "target": "/authentication_logout/", + "targets": [], + "value": "" + }, { + "id": "pod-get-url", + "comment": "Récupère et stocke dans la variable podURL la valeur de l'url", + "command": "executeScript", + "target": "return document.URL;", + "targets": [], + "value": "podURL" + }, { + "id": "pod-check-url", + "comment": "Vérifie qu'il y a bien redirection vers page d'accueil et donc connexion", + "command": "assert", + "target": "podURL", + "targets": [], + "value": "http://localhost:9090/" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-deconnexion"] + }], + "urls": ["http://localhost:9090/"], + "plugins": [] +} diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index ccdfac19a3..3da8386c2b 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -5,17 +5,22 @@ import json import os +import shutil +from urllib.parse import urljoin from django.conf import settings from django.contrib.auth.models import User from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.test.utils import override_settings +from django.core.files.temp import NamedTemporaryFile from pod.video_encode_transcript.encode import encode_video from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By from selenium.webdriver.firefox.webdriver import WebDriver from selenium.webdriver.support.ui import Select +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.keys import Keys from pod.video.models import Video, Type @@ -140,6 +145,7 @@ def run_commands(self, driver, url): "verifyElementNotPresent": self.verifyElementNotPresent, "waitForTextPresent": self.waitForTextPresent, "waitForTextNotPresent": self.waitForTextNotPresent, + "assert": self.assertSimple, "assertText": self.assertText, "assertNotText": self.assertNotText, "assertValue": self.assertValue, @@ -149,6 +155,7 @@ def run_commands(self, driver, url): "mouseOut": self.mouseOut, "sendKeys": self.sendKeys, "executeScript": self.executeScript, + "waitForElementVisible": self.waitForElementVisible, } for command in self.test_data.get("commands", []): @@ -186,7 +193,8 @@ def open(self, driver, target, value="", url=""): driver (WebDriver): The WebDriver instance for interacting with the web page. target (str): The URL to open. """ - driver.get(self.base_url + "/" + target) + url = urljoin(self.base_url, target) + driver.get(url) def click(self, driver, target, value="", url=""): """ @@ -386,6 +394,10 @@ def waitForTextNotPresent(self, driver, text, target="", value="", url=""): print("waitForTextNotPresent: ", repr(text), "present") raise + def assertSimple(self, driver, target, value="", url=""): + target_value = find_element(driver, target).get_attribute("value") + assert(target_value == value) + def assertText(self, driver, target, value="", url=""): """ Assert that the text of a specified web element matches the expected value. @@ -550,6 +562,10 @@ def executeScript(self, driver, target, value="", url=""): """ driver.execute_script(target) + def waitForElementVisible(self, driver, target, value="", url=""): + element = find_element(driver, target) + WebDriverWait(driver, 30).until(EC.visibility_of_element_located(element)) + class SeleniumTestSuite(object): """A Selenium Test Suite""" @@ -629,19 +645,44 @@ def test_selenium_suites(self): """Run Selenium Test Suites from Side files in all installed apps.""" self.selenium.get(f"{self.live_server_url}/") self.run_initial_tests() - self.run_all_tests() + time.sleep(1) + self.run_all_simple_tests() + time.sleep(1) + self.run_initial_staff_tests() + time.sleep(1) + self.run_all_staff_tests() def initialize_data(self): """Initialize custom test data.""" - self.user = User.objects.create_user(username="user", password="user") + self.user_simple = User.objects.create_user(username="user", password="user") + self.user_staff = User.objects.create_user(username="staff_user", password="user") + self.user_staff.is_staff = True + self.user_staff.save() + # copy video file + self.video = Video.objects.create( title="first-video", - owner=self.user, - video="pod/main/static/video_test/pod.mp4", + owner=self.user_simple, + is_draft=False, + type=Type.objects.get(id=1), + ) + tempfile = NamedTemporaryFile(delete=True) + self.video.video.save("test.mp4", tempfile) + dest = os.path.join(settings.MEDIA_ROOT, self.video.video.name) + shutil.copyfile("pod/main/static/video_test/pod.mp4", dest) + self.video_staff = Video.objects.create( + title="video-staff", + owner=self.user_staff, is_draft=False, type=Type.objects.get(id=1), ) + tempfile = NamedTemporaryFile(delete=True) + self.video_staff.video.save("test.mp4", tempfile) + dest = os.path.join(settings.MEDIA_ROOT, self.video_staff.video.name) + shutil.copyfile("pod/main/static/video_test/pod.mp4", dest) + encode_video(self.video.id) + encode_video(self.video_staff.id) def run_suite(self, suite_name, suite_url): """ @@ -678,7 +719,34 @@ def run_initial_tests(self): self.run_single_suite(cookies_test_path) self.run_single_suite(login_test_path) - def run_all_tests(self): + def run_initial_staff_tests(self): + """Run initial Selenium test suites for cookies and login.""" + initial_tests_dir = os.path.join( + os.path.dirname(__file__), "init_integration_tests") + deconnexion_path = os.path.join(initial_tests_dir, "deconnexion.side") + login_test_path = os.path.join(initial_tests_dir, "connexion_staff.side") + self.run_single_suite(deconnexion_path) + self.run_single_suite(login_test_path) + + def run_all_simple_tests(self): + """ + Run Selenium test suites in all installed apps. + + This method searches for test suites in integration_tests/tests directories of all installed apps and runs them. + """ + for app in settings.INSTALLED_APPS: + app_module = __import__(app, fromlist=["integration_tests"]) + integration_tests_dir = os.path.join( + os.path.dirname(app_module.__file__), "integration_tests" + ) + if os.path.exists(integration_tests_dir): + tests_dir = os.path.join(integration_tests_dir, "tests") + if os.path.exists(tests_dir): + print(f"Running Selenium tests in {app}...") + self.run_tests_in_directory(tests_dir, suffixe = "_simple") + print("All simple tests are DONE") + + def run_all_staff_tests(self): """ Run Selenium test suites in all installed apps. @@ -693,10 +761,10 @@ def run_all_tests(self): tests_dir = os.path.join(integration_tests_dir, "tests") if os.path.exists(tests_dir): print(f"Running Selenium tests in {app}...") - self.run_tests_in_directory(tests_dir) - print("All tests are DONE") + self.run_tests_in_directory(tests_dir, suffixe = "_staff") + print("All simple tests are DONE") - def run_tests_in_directory(self, directory): + def run_tests_in_directory(self, directory, suffixe = ""): """ Run Selenium test suites in the specified directory. @@ -705,7 +773,7 @@ def run_tests_in_directory(self, directory): """ for root, dirs, files in os.walk(directory): for filename in files: - if filename.endswith(".side"): + if filename.endswith("%s.side" % suffixe): suite_name = os.path.join(root, filename) self.run_single_suite(suite_name) @@ -719,6 +787,7 @@ def run_single_suite(self, suite_name): try: suite_url = self.live_server_url self.run_suite(suite_name, suite_url) + PodSeleniumTests.selenium.save_screenshot(f"{suite_name}-info_screen.png") except Exception: PodSeleniumTests.selenium.save_screenshot(f"{suite_name}-error_screen.png") print("ERROR !") diff --git a/pod/main/integration_tests/sides/pod-configuration.side b/pod/main/integration_tests/sides/pod-configuration.side deleted file mode 100644 index b895cacc2d..0000000000 --- a/pod/main/integration_tests/sides/pod-configuration.side +++ /dev/null @@ -1,131 +0,0 @@ -{ - "id": "96bdfd57-c9ad-4b38-b5f6-c32a68957e33", - "version": "2.0", - "name": "pod3.4.0", - "url": "http://localhost:9090", - "tests": [{ - "id": "f27edc9b-9e82-4944-a554-acdf4008d084", - "name": "switch_to_dark_mode", - "commands": [{ - "id": "75113f62-909d-4cfb-bd66-654892f501b2", - "comment": "", - "command": "open", - "target": "/", - "targets": [], - "value": "" - }, { - "id": "395352bc-9f40-4ce8-9706-6e1160a73340", - "comment": "", - "command": "click", - "target": "id=okcookie", - "targets": [ - ["id=okcookie", "id"], - ["css=#okcookie", "css:finder"], - ["xpath=//button[@id='okcookie']", "xpath:attributes"], - ["xpath=//div[@id='cookieModal']/div/div/div/div/button", "xpath:idRelative"], - ["xpath=//div[3]/div/div/div/div/button", "xpath:position"], - ["xpath=//button[contains(.,'J’ai compris')]", "xpath:innerText"] - ], - "value": "" - }, { - "id": "5824164e-d42a-46c5-9608-850b3a2804e1", - "comment": "", - "command": "click", - "target": "id=pod-param-buttons__button", - "targets": [ - ["id=pod-param-buttons__button", "id"], - ["css=#pod-param-buttons__button", "css:finder"], - ["xpath=//button[@id='pod-param-buttons__button']", "xpath:attributes"], - ["xpath=//li[@id='pod-param-buttons']/button", "xpath:idRelative"], - ["xpath=//nav/div/ul/li/button", "xpath:position"] - ], - "value": "" - }, { - "id": "9f3a14ec-b937-4bd5-98d1-9d650dfc1b77", - "comment": "", - "command": "click", - "target": "css=.theme-switch > .slider", - "targets": [ - ["css=.theme-switch > .slider", "css:finder"], - ["xpath=//div[@id='pod-navbar__menusettings']/div[2]/ul/li[2]/label/span[2]/span", "xpath:idRelative"], - ["xpath=//span[2]/span", "xpath:position"] - ], - "value": "" - }, { - "id": "eaa1d134-f372-4a8b-84d0-c3bb0689c03f", - "comment": "", - "command": "click", - "target": "css=#pod-navbar__menusettings > .offcanvas-body", - "targets": [ - ["css=#pod-navbar__menusettings > .offcanvas-body", "css:finder"], - ["xpath=//div[@id='pod-navbar__menusettings']/div[2]", "xpath:idRelative"], - ["xpath=//li/div/div[2]", "xpath:position"] - ], - "value": "" - }] - }, { - "id": "70df9a16-53b3-4a29-845f-f8264ec89cb3", - "name": "switch_to_dyslexia_mode", - "commands": [{ - "id": "a5d16f0f-5b79-4e39-baa2-9d5d26935229", - "comment": "", - "command": "open", - "target": "/", - "targets": [], - "value": "" - }, { - "id": "a3b14c5e-9c01-4c6b-aa1f-fb56669b83cd", - "comment": "", - "command": "setWindowSize", - "target": "1303x889", - "targets": [], - "value": "" - }, { - "id": "a472f852-2116-4c9b-b1f0-13417fd0ceb3", - "comment": "", - "command": "click", - "target": "id=pod-param-buttons__button", - "targets": [ - ["id=pod-param-buttons__button", "id"], - ["css=#pod-param-buttons__button", "css:finder"], - ["xpath=//button[@id='pod-param-buttons__button']", "xpath:attributes"], - ["xpath=//li[@id='pod-param-buttons']/button", "xpath:idRelative"], - ["xpath=//nav/div/ul/li/button", "xpath:position"] - ], - "value": "" - }, { - "id": "9be70e4b-70c6-4ae2-b9b2-0fd4b8d3e664", - "comment": "", - "command": "click", - "target": "css=.dyslexia-switch > .slider", - "targets": [ - ["css=.dyslexia-switch > .slider", "css:finder"], - ["xpath=//li[@id='dyslexia-switch-wrapper']/label/span[2]/span", "xpath:idRelative"], - ["xpath=//li[3]/label/span[2]/span", "xpath:position"] - ], - "value": "" - }, { - "id": "63742661-2b25-4525-9f40-55b5e2343160", - "comment": "", - "command": "click", - "target": "css=#pod-navbar__menusettings .btn-close", - "targets": [ - ["css=#pod-navbar__menusettings .btn-close", "css:finder"], - ["xpath=(//button[@type='button'])[4]", "xpath:attributes"], - ["xpath=//div[@id='pod-navbar__menusettings']/div/button", "xpath:idRelative"], - ["xpath=//li/div/div/button", "xpath:position"] - ], - "value": "" - }] - }], - "suites": [{ - "id": "b6a92037-b365-4211-adea-2b6725c3ed63", - "name": "Default Suite", - "persistSession": false, - "parallel": false, - "timeout": 300, - "tests": ["f27edc9b-9e82-4944-a554-acdf4008d084"] - }], - "urls": ["http://localhost:9090/"], - "plugins": [] -} \ No newline at end of file diff --git a/pod/main/integration_tests/tests/lang.side b/pod/main/integration_tests/tests/lang_simple.side similarity index 100% rename from pod/main/integration_tests/tests/lang.side rename to pod/main/integration_tests/tests/lang_simple.side diff --git a/pod/main/integration_tests/tests/search.side b/pod/main/integration_tests/tests/search_simple.side similarity index 100% rename from pod/main/integration_tests/tests/search.side rename to pod/main/integration_tests/tests/search_simple.side diff --git a/pod/video/integration_tests/tests/comment.side b/pod/video/integration_tests/tests/comment_simple.side similarity index 98% rename from pod/video/integration_tests/tests/comment.side rename to pod/video/integration_tests/tests/comment_simple.side index 840be28b7e..a1819d890c 100644 --- a/pod/video/integration_tests/tests/comment.side +++ b/pod/video/integration_tests/tests/comment_simple.side @@ -190,6 +190,6 @@ "tests": ["pod-add-delete-comment"] } ], - "urls": ["http://localhost:9090/", "https://pod-test.univ-lille.fr/"], + "urls": ["http://localhost:9090/"], "plugins": [] } From 8a14fef4429915a37774156ce78c58140d84de86 Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Fri, 17 Nov 2023 18:00:46 +0100 Subject: [PATCH 52/52] workon selenium python test instead of side selenium files --- .github/workflows/pod.yml | 2 +- .../init_integration_tests/cookies.side | 61 +- .../side_init_test_connexion.py | 31 + .../side_init_test_connexionstaff.py | 31 + .../side_init_test_cookies.py | 24 + .../side_init_test_deconnexion.py | 25 + .../selenium_pod_integration_tests.py | 754 ++--------------- ...elenium_pod_integration_tests_side.py.back | 795 ++++++++++++++++++ .../tests/search_simple.side | 162 ++-- .../tests/side_anonyme_test_lang.py | 34 + .../tests/side_anonyme_test_search.py | 27 + 11 files changed, 1156 insertions(+), 790 deletions(-) create mode 100644 pod/main/integration_tests/init_integration_tests/side_init_test_connexion.py create mode 100644 pod/main/integration_tests/init_integration_tests/side_init_test_connexionstaff.py create mode 100644 pod/main/integration_tests/init_integration_tests/side_init_test_cookies.py create mode 100644 pod/main/integration_tests/init_integration_tests/side_init_test_deconnexion.py create mode 100644 pod/main/integration_tests/selenium_pod_integration_tests_side.py.back create mode 100644 pod/main/integration_tests/tests/side_anonyme_test_lang.py create mode 100644 pod/main/integration_tests/tests/side_anonyme_test_search.py diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index 6ac2400f3a..f38fb473ee 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -54,7 +54,7 @@ jobs: - name: Flake8 compliance run: | - flake8 --max-complexity=7 --ignore=E501,W503,E203 --exclude .git,pod/*/migrations/*.py,*_settings.py + flake8 --max-complexity=7 --ignore=E501,W503,E203 --exclude .git,pod/*/migrations/*.py,*_settings.py,side_*.py - name: Runs Elasticsearch uses: elastic/elastic-github-actions/elasticsearch@refactor_with_plugins diff --git a/pod/main/integration_tests/init_integration_tests/cookies.side b/pod/main/integration_tests/init_integration_tests/cookies.side index 781b6663af..ca4357d1ab 100644 --- a/pod/main/integration_tests/init_integration_tests/cookies.side +++ b/pod/main/integration_tests/init_integration_tests/cookies.side @@ -2,38 +2,33 @@ "id": "f060a750-1757-4b5d-8ed0-ce6b1c49b62b", "version": "2.0", "name": "pod-front", - "url": "http://localhost:9090/", - "tests": [ - { - "id": "pod-accept-cookies", - "name": "cookies", - "commands": [ - { - "id": "pod-open-home", - "comment": "Ouvre la page d'accueil", - "command": "open", - "target": "/", - "targets": [], - "value": "" - }, - { - "id": "pod-accept-cookies", - "comment": "Valide les cookies du navigateur", - "command": "cookies_commands" - } - ] - } - ], - "suites": [ - { - "id": "pod-front", - "name": "Esup Pod test selenium", - "persistSession": false, - "parallel": false, - "timeout": 300, - "tests": ["pod-accept-cookies"] - } - ], + "tests": [{ + "id": "pod-accept-cookies", + "name": "cookies", + "commands": [{ + "id": "pod-open-home", + "comment": "Ouvre la page d'accueil", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "pod-accept-cookies-command", + "comment": "Valide les cookies du navigateur", + "command": "click", + "target": "id=okcookie", + "targets": [], + "value": "" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-accept-cookies"] + }], "urls": ["http://localhost:9090/"], "plugins": [] -} +} \ No newline at end of file diff --git a/pod/main/integration_tests/init_integration_tests/side_init_test_connexion.py b/pod/main/integration_tests/init_integration_tests/side_init_test_connexion.py new file mode 100644 index 0000000000..a836dd186a --- /dev/null +++ b/pod/main/integration_tests/init_integration_tests/side_init_test_connexion.py @@ -0,0 +1,31 @@ +# Generated by Selenium IDE +import pytest +import time +import json +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +class TestConnexion(): + def setup_method(self, method): + self.driver = webdriver.Firefox() + self.vars = {} + + def teardown_method(self, method): + self.driver.quit() + + def test_connexion(self): + self.driver.get("http://localhost:9090/") + self.driver.find_element(By.XPATH, "//li[@id=\'nav-authentication\']/a/i").click() + self.driver.find_element(By.ID, "id_username").click() + self.driver.find_element(By.ID, "id_username").send_keys("user") + self.driver.find_element(By.ID, "id_password").click() + self.driver.find_element(By.ID, "id_password").send_keys("user") + self.driver.find_element(By.XPATH, "//form[@id=\'login-form\']/button").click() + self.vars["podURL"] = self.driver.execute_script("return document.URL;") + assert(self.vars["podURL"] == "http://localhost:9090/") + diff --git a/pod/main/integration_tests/init_integration_tests/side_init_test_connexionstaff.py b/pod/main/integration_tests/init_integration_tests/side_init_test_connexionstaff.py new file mode 100644 index 0000000000..c10c89cba9 --- /dev/null +++ b/pod/main/integration_tests/init_integration_tests/side_init_test_connexionstaff.py @@ -0,0 +1,31 @@ +# Generated by Selenium IDE +import pytest +import time +import json +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +class TestConnexionstaff(): + def setup_method(self, method): + self.driver = webdriver.Firefox() + self.vars = {} + + def teardown_method(self, method): + self.driver.quit() + + def test_connexionstaff(self): + self.driver.get("http://localhost:9090/") + self.driver.find_element(By.XPATH, "//li[@id=\'nav-authentication\']/a/i").click() + self.driver.find_element(By.ID, "id_username").click() + self.driver.find_element(By.ID, "id_username").send_keys("staff_user") + self.driver.find_element(By.ID, "id_password").click() + self.driver.find_element(By.ID, "id_password").send_keys("user") + self.driver.find_element(By.XPATH, "//form[@id=\'login-form\']/button").click() + self.vars["podURL"] = self.driver.execute_script("return document.URL;") + assert(self.vars["podURL"] == "http://localhost:9090/") + diff --git a/pod/main/integration_tests/init_integration_tests/side_init_test_cookies.py b/pod/main/integration_tests/init_integration_tests/side_init_test_cookies.py new file mode 100644 index 0000000000..4300a1280f --- /dev/null +++ b/pod/main/integration_tests/init_integration_tests/side_init_test_cookies.py @@ -0,0 +1,24 @@ +# Generated by Selenium IDE +import pytest +import time +import json +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +class TestCookies(): + def setup_method(self, method): + self.driver = webdriver.Firefox() + self.vars = {} + + def teardown_method(self, method): + self.driver.quit() + + def test_cookies(self): + self.driver.get("http://localhost:9090/") + self.driver.find_element(By.ID, "okcookie").click() + diff --git a/pod/main/integration_tests/init_integration_tests/side_init_test_deconnexion.py b/pod/main/integration_tests/init_integration_tests/side_init_test_deconnexion.py new file mode 100644 index 0000000000..8d46848b9a --- /dev/null +++ b/pod/main/integration_tests/init_integration_tests/side_init_test_deconnexion.py @@ -0,0 +1,25 @@ +# Generated by Selenium IDE +import pytest +import time +import json +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +class TestDeconnexion(): + def setup_method(self, method): + self.driver = webdriver.Firefox() + self.vars = {} + + def teardown_method(self, method): + self.driver.quit() + + def test_deconnexion(self): + self.driver.get("http://localhost:9090/authentication_logout/") + self.vars["podURL"] = self.driver.execute_script("return document.URL;") + assert(self.vars["podURL"] == "http://localhost:9090/") + diff --git a/pod/main/integration_tests/selenium_pod_integration_tests.py b/pod/main/integration_tests/selenium_pod_integration_tests.py index 3da8386c2b..d4e9f128a1 100644 --- a/pod/main/integration_tests/selenium_pod_integration_tests.py +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -3,10 +3,9 @@ * run with 'python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests' """ -import json +import importlib import os import shutil -from urllib.parse import urljoin from django.conf import settings from django.contrib.auth.models import User @@ -15,609 +14,15 @@ from django.core.files.temp import NamedTemporaryFile from pod.video_encode_transcript.encode import encode_video -from selenium.common.exceptions import NoSuchElementException -from selenium.webdriver.common.by import By from selenium.webdriver.firefox.webdriver import WebDriver -from selenium.webdriver.support.ui import Select -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.keys import Keys from pod.video.models import Video, Type -import time target_cache = {} current_side_file = None -def parse_target(target): - """ - Parse the target string to determine the Selenium locator type and value. - - Args: - target (str): The target element identifier, which can start with 'link=', '//', 'xpath=', 'css=', 'id=', or 'name='. - - Returns: - tuple: A tuple containing the Selenium locator type (e.g., By.LINK_TEXT, By.XPATH, By.CSS_SELECTOR, By.ID, By.NAME) and the corresponding value. - - Returns: - tuple: A tuple containing the Selenium locator type and the corresponding value, or (None, None) if the target identifier format is not recognized. - """ - if target.startswith("link="): - return By.LINK_TEXT, target[5:] - elif target.startswith("//"): - return By.XPATH, target - elif target.startswith("xpath="): - return By.XPATH, target[6:] - elif target.startswith("css="): - return By.CSS_SELECTOR, target[4:] - elif target.startswith("id="): - return By.ID, target[3:] - elif target.startswith("name="): - return By.NAME, target[5:] - else: - return None, None - - -def find_element(driver, target): - """ - Find and return a web element using various locator strategies (e.g., By.ID, By.XPATH, By.CSS_SELECTOR). - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The target element identifier, which can start with 'link=', '//', 'xpath=', 'css=', 'id=', or 'name='. - - Returns: - WebElement: The located web element. - - Raises: - NoSuchElementException: If the specified element is not found on the web page. - Exception: If the target identifier format is not recognized. - """ - if target in target_cache: - target = target_cache[target] - - locator, value = parse_target(target) - - if locator is not None: - try: - return driver.find_element(locator, value) - except NoSuchElementException: - result = driver.find_element(locator, value.lower()) - target_cache[target] = f"{locator}={value.lower()}" - print(f"label {value} is being cached as {target_cache[target]}") - return result - else: - direct = ( - driver.find_element(By.NAME, target) - or driver.find_element(By.ID, target) - or driver.find_element(By.LINK_TEXT, target) - ) - if direct: - return direct - raise Exception("Don't know how to find %s" % target) - - -class SeleniumTestCase(object): - """A Single Selenium Test Case""" - - def __init__(self, test_data, callback=None): - """ - Initialize a SeleniumTestCase instance. - - Args: - test_data (dict): Test case data, including name, URL, and commands. - callback (callable, optional): Callback function to be executed after the test. - """ - self.test_data = test_data - self.callback = callback - self.base_url = None - self.commands = [] - - def run(self, driver, url): - """ - Run the Selenium test case. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - url (str): The base URL for the test case. - """ - self.base_url = url - print(f'Running test: {self.test_data["name"]}') - self.run_commands(driver, url) - if self.callback: - self.callback(driver.page_source) - - def run_commands(self, driver, url): - COMMAND_MAPPING = { - "open": self.open, - "click": self.click, - "pause": self.pause, - "cookies_commands": self.cookies_commands, - "clickAndWait": self.clickAndWait, - "type": self.type, - "select": self.select, - "verifyTextPresent": self.verifyTextPresent, - "verifyTextNotPresent": self.verifyTextNotPresent, - "assertElementPresent": self.assertElementPresent, - "verifyElementPresent": self.verifyElementPresent, - "verifyElementNotPresent": self.verifyElementNotPresent, - "waitForTextPresent": self.waitForTextPresent, - "waitForTextNotPresent": self.waitForTextNotPresent, - "assert": self.assertSimple, - "assertText": self.assertText, - "assertNotText": self.assertNotText, - "assertValue": self.assertValue, - "assertNotValue": self.assertNotValue, - "verifyValue": self.verifyValue, - "mouseOver": self.mouseOver, - "mouseOut": self.mouseOut, - "sendKeys": self.sendKeys, - "executeScript": self.executeScript, - "waitForElementVisible": self.waitForElementVisible, - } - - for command in self.test_data.get("commands", []): - command_name = command.get("command", "") - target = command.get("target", "") - value = command.get("value", "") - - if command_name in COMMAND_MAPPING: - COMMAND_MAPPING[command_name]( - driver=driver, target=target, value=value, url=url - ) - else: - print(f"Command {command_name} not supported") - - def cookies_commands(self, driver, url, value="", target=""): - """ - Execute custom commands to handle cookies acceptance on website. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - url (str): The base URL for the test case. - """ - with open( - "pod/main/integration_tests/commands/cookies_commands.side", "r" - ) as side_file: - side_data = json.load(side_file) - self.test_data["commands"] = side_data.get("commands", []) - self.run_commands(driver, url) - - def open(self, driver, target, value="", url=""): - """ - Open a URL in the WebDriver. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The URL to open. - """ - url = urljoin(self.base_url, target) - driver.get(url) - - def click(self, driver, target, value="", url=""): - """ - Click on a web element identified by a target. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element to be clicked. - """ - element = find_element(driver, target) - element_tag = element.tag_name - without_scroll_elements = { - "button", - "submit", - "textarea", - "input", - "div", - "span", - "i", - } - time.sleep(1) - if element_tag not in without_scroll_elements: - driver.execute_script("arguments[0].scrollIntoView();", element) - element.click() - else: - driver.execute_script("arguments[0].click();", element) - - def pause(self, driver, target, value="", url=""): - """ - Make a pause during target value. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The during value to wait. - """ - driver.implicitly_wait(target) - - def clickAndWait(self, driver, target, value="", url=""): - """ - Click on a web element identified by a target and wait for a response. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element to be clicked. - """ - self.click(driver, target) - - def type(self, driver, target, value="", url=""): - """ - Simulate typing text into an input field on a web page. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the input field. - value (str, optional): The value to be typed into the input field. - """ - element = find_element(driver, target) - element_tag = element.tag_name - without_scroll_elements = { - "button", - "submit", - "textarea", - "input", - "div", - "span", - "i", - } - time.sleep(1) - if element_tag not in without_scroll_elements: - driver.execute_script("arguments[0].scrollIntoView();", element) - element.click() - element.clear() - else: - driver.execute_script("arguments[0].click();", element) - element.send_keys(Keys.CONTROL, 'a') - element.send_keys(Keys.DELETE) - element.send_keys(value) - - def select(self, driver, target, value, url=""): - """ - Select an option from a dropdown list on a web page. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the dropdown element. - value (str): The option value to be selected. - """ - element = find_element(driver, target) - if value.startswith("label="): - Select(element).select_by_visible_text(value[6:]) - else: - msg = "Don't know how to select %s on %s" - raise Exception(msg % (value, target)) - - def verifyTextPresent(self, driver, text, target="", value="", url=""): - """ - Verify if the specified text is present in the page source. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - text (str): The text to be verified for presence. - """ - try: - source = driver.page_source - assert bool(text in source) - except Exception: - print("verifyTextPresent: ", repr(text), "not present in", repr(source)) - raise - - def verifyTextNotPresent(self, driver, text, target="", value="", url=""): - """ - Verify if the specified text is not present in the page source. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - text (str): The text to be verified for absence. - """ - try: - assert not bool(text in driver.page_source) - except Exception: - print("verifyNotTextPresent: ", repr(text), "present") - raise - - def assertElementPresent(self, driver, target, value="", url=""): - """ - Assert the presence of a specified web element. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element to be verified for presence. - """ - try: - assert bool(find_element(driver, target)) - except Exception: - print("assertElementPresent: ", repr(target), "not present") - raise - - def verifyElementPresent(self, driver, target, value="", url=""): - """ - Verify the presence of a specified web element on a web page. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element to be verified for presence. - """ - try: - assert bool(find_element(driver, target)) - except Exception: - print("verifyElementPresent: ", repr(target), "not present") - raise - - def verifyElementNotPresent(self, driver, target, value="", url=""): - """ - Verify the absence of a specified web element on a web page. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element to be verified for absence. - """ - present = True - try: - find_element(driver, target) - except NoSuchElementException: - present = False - - try: - assert not present - except Exception: - print("verifyElementNotPresent: ", repr(target), "present") - raise - - def waitForTextPresent(self, driver, text, target="", value="", url=""): - """ - Wait for the specified text to be present in the page source. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - text (str): The text to wait for in the page source. - """ - try: - assert bool(text in driver.page_source) - except Exception: - print("waitForTextPresent: ", repr(text), "not present") - raise - - def waitForTextNotPresent(self, driver, text, target="", value="", url=""): - """ - Wait for the specified text to be absent in the page source. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - text (str): The text to wait for to be absent in the page source. - """ - try: - assert not bool(text in driver.page_source) - except Exception: - print("waitForTextNotPresent: ", repr(text), "present") - raise - - def assertSimple(self, driver, target, value="", url=""): - target_value = find_element(driver, target).get_attribute("value") - assert(target_value == value) - - def assertText(self, driver, target, value="", url=""): - """ - Assert that the text of a specified web element matches the expected value. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element whose text needs to be asserted. - value (str, optional): The expected text value to compare against (default is an empty string). - """ - try: - target_value = find_element(driver, target).text - print(" assertText target value =" + repr(target_value)) - if value.startswith("exact:"): - assert target_value == value[len("exact:") :] - else: - assert target_value == value - except Exception: - print( - "assertText: ", - repr(target), - repr(find_element(driver, target).get_attribute("value")), - repr(value), - ) - raise - - def assertNotText(self, driver, target, value="", url=""): - """ - Assert that the text of a specified web element does not match the expected value. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element whose text needs to be asserted. - value (str, optional): The expected text value to compare against (default is an empty string). - """ - try: - target_value = find_element(driver, target).text - print(" assertNotText target value =" + repr(target_value)) - if value.startswith("exact:"): - assert target_value != value[len("exact:") :] - else: - assert target_value != value - except Exception: - print( - "assertNotText: ", - repr(target), - repr(find_element(driver, target).get_attribute("value")), - repr(value), - ) - raise - - def assertValue(self, driver, target, value="", url=""): - """ - Assert that the value attribute of a specified web element matches the expected value. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element whose value attribute needs to be asserted. - value (str, optional): The expected value to compare against (default is an empty string). - """ - try: - target_value = find_element(driver, target).get_attribute("value") - print(" assertValue target value =" + repr(target_value)) - assert target_value == value - except Exception: - print( - "assertValue: ", - repr(target), - repr(find_element(driver, target).get_attribute("value")), - repr(value), - ) - raise - - def assertNotValue(self, driver, target, value="", url=""): - """ - Assert that the value attribute of a specified web element does not match the expected value. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element whose value attribute needs to be asserted. - value (str, optional): The expected value to compare against (default is an empty string). - """ - try: - target_value = find_element(driver, target).get_attribute("value") - print(" assertNotValue target value =" + repr(target_value)) - assert target_value != value - except Exception: - print( - "assertNotValue: ", - repr(target), - repr(target_value), - repr(value), - ) - raise - - def verifyValue(self, driver, target, value="", url=""): - """ - Verify that the value attribute of a specified web element matches the expected value. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element whose value attribute needs to be verified. - value (str, optional): The expected value to compare against (default is an empty string). - """ - try: - target_value = find_element(driver, target).get_attribute("value") - print(" verifyValue target value =" + repr(target_value)) - assert target_value == value - except Exception: - print( - "verifyValue: ", - repr(target), - repr(find_element(driver, target).get_attribute("value")), - repr(value), - ) - raise - - def mouseOver(self, driver, target, value="", url=""): - """ - Simulate a mouse over event on the specified target element. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element whose value attribute needs to be verified. - """ - element = find_element(driver, target) - driver.execute_script( - "arguments[0].dispatchEvent(new Event('mouseover'))", element - ) - - def mouseOut(self, driver, target, value="", url=""): - """ - Simulate a mouse out event on the specified target element. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element whose value attribute needs to be verified. - """ - element = find_element(driver, target) - driver.execute_script( - "arguments[0].dispatchEvent(new Event('mouseout'))", element - ) - - def sendKeys(self, driver, target, value="", url=""): - """ - Send the specified text to the target element. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - target (str): The identifier for the web element whose value attribute needs to be verified. - value (str, optional): The text to send to the target element. - """ - element = find_element(driver, target) - element.send_keys(value) - - def executeScript(self, driver, target, value="", url=""): - """ - Execute a script script in the browser. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - script (str): The script code to execute. - """ - driver.execute_script(target) - - def waitForElementVisible(self, driver, target, value="", url=""): - element = find_element(driver, target) - WebDriverWait(driver, 30).until(EC.visibility_of_element_located(element)) - - -class SeleniumTestSuite(object): - """A Selenium Test Suite""" - - def __init__(self, filename, callback=None): - """ - Initialize a SeleniumTestSuite instance. - - Args: - filename (str): The name of the test suite JSON file. - callback (callable, optional): A callback function to be executed after each test (default is None). - """ - self.filename = filename - self.callback = callback - - self.tests = [] - - with open(filename, "r") as json_file: - json_data = json.load(json_file) - self.title = json_data.get("name", "Untitled Suite") - self.tests = [ - (test_data.get("name", "Untitled Test"), test_data) - for test_data in json_data.get("tests", []) - ] - - def run(self, driver, url): - """ - Run the Selenium test suite with the specified WebDriver instance and URL. - - Args: - driver (WebDriver): The WebDriver instance for interacting with the web page. - url (str): The base URL to be used for the tests. - """ - for test_title, test_data in self.tests: - try: - test = SeleniumTestCase(test_data, self.callback) - test.run(driver, url) - except Exception as e: - print(f"Error in {test_title} ({test.filename}): {e}") - raise - - def __repr__(self): - """ - Return a string representation of the SeleniumTestSuite. - - Returns: - str: A string representation of the test suite, including its title and test titles. - """ - test_titles = "\n".join([title for title, _ in self.tests]) - return f"{self.title}\n{test_titles}" - - class PodSeleniumTests(StaticLiveServerTestCase): """Tests the integration of Pod application with Selenium from side files""" @@ -631,35 +36,36 @@ def setUp(self): def setUpClass(cls): """Create the WebDriver for all Selenium tests.""" super().setUpClass() - cls.selenium = WebDriver() - cls.selenium.implicitly_wait(10) + cls.driver = WebDriver() + cls.vars = {} + cls.driver.implicitly_wait(10) @classmethod def tearDownClass(cls): """Close the WebDriver used.""" - cls.selenium.quit() + cls.driver.quit() super().tearDownClass() @override_settings(DEBUG=False) def test_selenium_suites(self): """Run Selenium Test Suites from Side files in all installed apps.""" - self.selenium.get(f"{self.live_server_url}/") + self.driver.get(f"{self.live_server_url}/") + # run initial test self.run_initial_tests() - time.sleep(1) - self.run_all_simple_tests() - time.sleep(1) - self.run_initial_staff_tests() - time.sleep(1) - self.run_all_staff_tests() + # run anonyme test + self.run_tests("anonyme") + # run simple user test + # run staff uesr test def initialize_data(self): """Initialize custom test data.""" self.user_simple = User.objects.create_user(username="user", password="user") - self.user_staff = User.objects.create_user(username="staff_user", password="user") - self.user_staff.is_staff = True - self.user_staff.save() + self.user_staff = User.objects.create_user( + username="staff_user", + password="user", + is_staff=True + ) # copy video file - self.video = Video.objects.create( title="first-video", owner=self.user_simple, @@ -684,69 +90,76 @@ def initialize_data(self): encode_video(self.video.id) encode_video(self.video_staff.id) - def run_suite(self, suite_name, suite_url): + def run_suite(self, suite_name, prefixe=""): """ Run a Selenium test suite with the specified name and URL. Args: - suite_name (str): The name of the test suite JSON file. + suite_name (str): The name of the test suite python file. suite_url (str): The base URL for the test suite. """ global current_side_file print(f"Running test suite: {suite_name}") - with open(suite_name, "r") as json_file: - suite_data = json.load(json_file) - suite_tests = suite_data.get("tests", []) - - for test_data in suite_tests: - test_case = SeleniumTestCase(test_data) - try: - test_case.run(PodSeleniumTests.selenium, suite_url) - except Exception: - PodSeleniumTests.selenium.save_screenshot( - f"{suite_name}-error_screen.png" - ) - current_side_file = suite_name - self.fail(f"Error in suite {suite_name}") + fname, ext = os.path.splitext(os.path.basename(suite_name)) + def_name = fname.replace(prefixe, "") + command_lines = [] + import_lines = [] + find_def_name = False + + with open(suite_name, 'r', encoding="utf8", errors='ignore') as fp: + # read all lines in a list + for l_no, line in enumerate(fp): + # check if string present on a current line + if line.startswith("from selenium"): + import_lines.append(line) + if line.find(def_name) != -1: + find_def_name = True + continue + if find_def_name: + if len(line.strip()) == 0 : + break + command_lines.append(self.format_line(line)) + + tempfile = suite_name.replace(fname, def_name) + print(tempfile) + with open(tempfile, "w") as f: + f.writelines(import_lines) + f.write("\n") + f.write("def %s(cls):\n" % def_name) + for command in command_lines: + f.write(" %s\n" % command) + f.write("\n") + + import_module = tempfile[tempfile.index('/pod/') + 1:] + import_module = import_module.replace("/", ".").replace(".py", "") + module = importlib.import_module(import_module) + module_fct = getattr(module, def_name) + module_fct(self) + os.remove(tempfile) + + def format_line(self, line): + formatted_line = line + formatted_line = formatted_line.strip().replace("undefined", "") + formatted_line = formatted_line.replace("self", "cls") + formatted_line = formatted_line.replace( + "http://localhost:9090", self.live_server_url + ) + return formatted_line def run_initial_tests(self): """Run initial Selenium test suites for cookies and login.""" initial_tests_dir = os.path.join( os.path.dirname(__file__), "init_integration_tests") - cookies_test_path = os.path.join(initial_tests_dir, "cookies.side") - login_test_path = os.path.join(initial_tests_dir, "connexion.side") - self.run_single_suite(cookies_test_path) - self.run_single_suite(login_test_path) - - def run_initial_staff_tests(self): - """Run initial Selenium test suites for cookies and login.""" - initial_tests_dir = os.path.join( - os.path.dirname(__file__), "init_integration_tests") - deconnexion_path = os.path.join(initial_tests_dir, "deconnexion.side") - login_test_path = os.path.join(initial_tests_dir, "connexion_staff.side") - self.run_single_suite(deconnexion_path) - self.run_single_suite(login_test_path) - - def run_all_simple_tests(self): - """ - Run Selenium test suites in all installed apps. - - This method searches for test suites in integration_tests/tests directories of all installed apps and runs them. - """ - for app in settings.INSTALLED_APPS: - app_module = __import__(app, fromlist=["integration_tests"]) - integration_tests_dir = os.path.join( - os.path.dirname(app_module.__file__), "integration_tests" - ) - if os.path.exists(integration_tests_dir): - tests_dir = os.path.join(integration_tests_dir, "tests") - if os.path.exists(tests_dir): - print(f"Running Selenium tests in {app}...") - self.run_tests_in_directory(tests_dir, suffixe = "_simple") - print("All simple tests are DONE") + cookies_test_path = os.path.join(initial_tests_dir, "side_init_test_cookies.py") + self.run_single_suite(cookies_test_path, "side_init_") + deconnexion_test_path = os.path.join( + initial_tests_dir, + "side_init_test_deconnexion.py" + ) + self.run_single_suite(deconnexion_test_path, "side_init_") - def run_all_staff_tests(self): + def run_tests(self, type): """ Run Selenium test suites in all installed apps. @@ -760,36 +173,37 @@ def run_all_staff_tests(self): if os.path.exists(integration_tests_dir): tests_dir = os.path.join(integration_tests_dir, "tests") if os.path.exists(tests_dir): - print(f"Running Selenium tests in {app}...") - self.run_tests_in_directory(tests_dir, suffixe = "_staff") - print("All simple tests are DONE") + print(f"Running Selenium {type} tests in {app}...") + self.run_tests_in_directory(tests_dir, type) + print("All %s tests are DONE" % type) - def run_tests_in_directory(self, directory, suffixe = ""): + def run_tests_in_directory(self, directory, type): """ Run Selenium test suites in the specified directory. Args: directory (str): The directory containing test suites. + type (str): The type of test to run. """ for root, dirs, files in os.walk(directory): for filename in files: - if filename.endswith("%s.side" % suffixe): + prefixe = "side_%s_" % type + if filename.startswith(prefixe): suite_name = os.path.join(root, filename) - self.run_single_suite(suite_name) + self.run_single_suite(suite_name, prefixe) - def run_single_suite(self, suite_name): + def run_single_suite(self, suite_name, suffixe): """ Run a single Selenium test suite. Args: - suite_name (str): The name of the test suite JSON file to run. + suite_name (str): The name of the test suite python file to run. """ try: - suite_url = self.live_server_url - self.run_suite(suite_name, suite_url) - PodSeleniumTests.selenium.save_screenshot(f"{suite_name}-info_screen.png") + self.run_suite(suite_name, suffixe) + PodSeleniumTests.driver.save_screenshot(f"{suite_name}-info_screen.png") except Exception: - PodSeleniumTests.selenium.save_screenshot(f"{suite_name}-error_screen.png") + PodSeleniumTests.driver.save_screenshot(f"{suite_name}-error_screen.png") print("ERROR !") print(f"Side path : {current_side_file}") self.fail(f"Error in suite {suite_name}") diff --git a/pod/main/integration_tests/selenium_pod_integration_tests_side.py.back b/pod/main/integration_tests/selenium_pod_integration_tests_side.py.back new file mode 100644 index 0000000000..3da8386c2b --- /dev/null +++ b/pod/main/integration_tests/selenium_pod_integration_tests_side.py.back @@ -0,0 +1,795 @@ +"""Esup-Pod integration tests. + +* run with 'python manage.py test -v 3 --settings=pod.main.test_settings pod.main.integration_tests.selenium_pod_integration_tests' +""" + +import json +import os +import shutil +from urllib.parse import urljoin + +from django.conf import settings +from django.contrib.auth.models import User +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from django.test.utils import override_settings +from django.core.files.temp import NamedTemporaryFile +from pod.video_encode_transcript.encode import encode_video + +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By +from selenium.webdriver.firefox.webdriver import WebDriver +from selenium.webdriver.support.ui import Select +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.keys import Keys + +from pod.video.models import Video, Type +import time + + +target_cache = {} +current_side_file = None + + +def parse_target(target): + """ + Parse the target string to determine the Selenium locator type and value. + + Args: + target (str): The target element identifier, which can start with 'link=', '//', 'xpath=', 'css=', 'id=', or 'name='. + + Returns: + tuple: A tuple containing the Selenium locator type (e.g., By.LINK_TEXT, By.XPATH, By.CSS_SELECTOR, By.ID, By.NAME) and the corresponding value. + + Returns: + tuple: A tuple containing the Selenium locator type and the corresponding value, or (None, None) if the target identifier format is not recognized. + """ + if target.startswith("link="): + return By.LINK_TEXT, target[5:] + elif target.startswith("//"): + return By.XPATH, target + elif target.startswith("xpath="): + return By.XPATH, target[6:] + elif target.startswith("css="): + return By.CSS_SELECTOR, target[4:] + elif target.startswith("id="): + return By.ID, target[3:] + elif target.startswith("name="): + return By.NAME, target[5:] + else: + return None, None + + +def find_element(driver, target): + """ + Find and return a web element using various locator strategies (e.g., By.ID, By.XPATH, By.CSS_SELECTOR). + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The target element identifier, which can start with 'link=', '//', 'xpath=', 'css=', 'id=', or 'name='. + + Returns: + WebElement: The located web element. + + Raises: + NoSuchElementException: If the specified element is not found on the web page. + Exception: If the target identifier format is not recognized. + """ + if target in target_cache: + target = target_cache[target] + + locator, value = parse_target(target) + + if locator is not None: + try: + return driver.find_element(locator, value) + except NoSuchElementException: + result = driver.find_element(locator, value.lower()) + target_cache[target] = f"{locator}={value.lower()}" + print(f"label {value} is being cached as {target_cache[target]}") + return result + else: + direct = ( + driver.find_element(By.NAME, target) + or driver.find_element(By.ID, target) + or driver.find_element(By.LINK_TEXT, target) + ) + if direct: + return direct + raise Exception("Don't know how to find %s" % target) + + +class SeleniumTestCase(object): + """A Single Selenium Test Case""" + + def __init__(self, test_data, callback=None): + """ + Initialize a SeleniumTestCase instance. + + Args: + test_data (dict): Test case data, including name, URL, and commands. + callback (callable, optional): Callback function to be executed after the test. + """ + self.test_data = test_data + self.callback = callback + self.base_url = None + self.commands = [] + + def run(self, driver, url): + """ + Run the Selenium test case. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + url (str): The base URL for the test case. + """ + self.base_url = url + print(f'Running test: {self.test_data["name"]}') + self.run_commands(driver, url) + if self.callback: + self.callback(driver.page_source) + + def run_commands(self, driver, url): + COMMAND_MAPPING = { + "open": self.open, + "click": self.click, + "pause": self.pause, + "cookies_commands": self.cookies_commands, + "clickAndWait": self.clickAndWait, + "type": self.type, + "select": self.select, + "verifyTextPresent": self.verifyTextPresent, + "verifyTextNotPresent": self.verifyTextNotPresent, + "assertElementPresent": self.assertElementPresent, + "verifyElementPresent": self.verifyElementPresent, + "verifyElementNotPresent": self.verifyElementNotPresent, + "waitForTextPresent": self.waitForTextPresent, + "waitForTextNotPresent": self.waitForTextNotPresent, + "assert": self.assertSimple, + "assertText": self.assertText, + "assertNotText": self.assertNotText, + "assertValue": self.assertValue, + "assertNotValue": self.assertNotValue, + "verifyValue": self.verifyValue, + "mouseOver": self.mouseOver, + "mouseOut": self.mouseOut, + "sendKeys": self.sendKeys, + "executeScript": self.executeScript, + "waitForElementVisible": self.waitForElementVisible, + } + + for command in self.test_data.get("commands", []): + command_name = command.get("command", "") + target = command.get("target", "") + value = command.get("value", "") + + if command_name in COMMAND_MAPPING: + COMMAND_MAPPING[command_name]( + driver=driver, target=target, value=value, url=url + ) + else: + print(f"Command {command_name} not supported") + + def cookies_commands(self, driver, url, value="", target=""): + """ + Execute custom commands to handle cookies acceptance on website. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + url (str): The base URL for the test case. + """ + with open( + "pod/main/integration_tests/commands/cookies_commands.side", "r" + ) as side_file: + side_data = json.load(side_file) + self.test_data["commands"] = side_data.get("commands", []) + self.run_commands(driver, url) + + def open(self, driver, target, value="", url=""): + """ + Open a URL in the WebDriver. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The URL to open. + """ + url = urljoin(self.base_url, target) + driver.get(url) + + def click(self, driver, target, value="", url=""): + """ + Click on a web element identified by a target. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be clicked. + """ + element = find_element(driver, target) + element_tag = element.tag_name + without_scroll_elements = { + "button", + "submit", + "textarea", + "input", + "div", + "span", + "i", + } + time.sleep(1) + if element_tag not in without_scroll_elements: + driver.execute_script("arguments[0].scrollIntoView();", element) + element.click() + else: + driver.execute_script("arguments[0].click();", element) + + def pause(self, driver, target, value="", url=""): + """ + Make a pause during target value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The during value to wait. + """ + driver.implicitly_wait(target) + + def clickAndWait(self, driver, target, value="", url=""): + """ + Click on a web element identified by a target and wait for a response. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be clicked. + """ + self.click(driver, target) + + def type(self, driver, target, value="", url=""): + """ + Simulate typing text into an input field on a web page. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the input field. + value (str, optional): The value to be typed into the input field. + """ + element = find_element(driver, target) + element_tag = element.tag_name + without_scroll_elements = { + "button", + "submit", + "textarea", + "input", + "div", + "span", + "i", + } + time.sleep(1) + if element_tag not in without_scroll_elements: + driver.execute_script("arguments[0].scrollIntoView();", element) + element.click() + element.clear() + else: + driver.execute_script("arguments[0].click();", element) + element.send_keys(Keys.CONTROL, 'a') + element.send_keys(Keys.DELETE) + element.send_keys(value) + + def select(self, driver, target, value, url=""): + """ + Select an option from a dropdown list on a web page. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the dropdown element. + value (str): The option value to be selected. + """ + element = find_element(driver, target) + if value.startswith("label="): + Select(element).select_by_visible_text(value[6:]) + else: + msg = "Don't know how to select %s on %s" + raise Exception(msg % (value, target)) + + def verifyTextPresent(self, driver, text, target="", value="", url=""): + """ + Verify if the specified text is present in the page source. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + text (str): The text to be verified for presence. + """ + try: + source = driver.page_source + assert bool(text in source) + except Exception: + print("verifyTextPresent: ", repr(text), "not present in", repr(source)) + raise + + def verifyTextNotPresent(self, driver, text, target="", value="", url=""): + """ + Verify if the specified text is not present in the page source. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + text (str): The text to be verified for absence. + """ + try: + assert not bool(text in driver.page_source) + except Exception: + print("verifyNotTextPresent: ", repr(text), "present") + raise + + def assertElementPresent(self, driver, target, value="", url=""): + """ + Assert the presence of a specified web element. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be verified for presence. + """ + try: + assert bool(find_element(driver, target)) + except Exception: + print("assertElementPresent: ", repr(target), "not present") + raise + + def verifyElementPresent(self, driver, target, value="", url=""): + """ + Verify the presence of a specified web element on a web page. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be verified for presence. + """ + try: + assert bool(find_element(driver, target)) + except Exception: + print("verifyElementPresent: ", repr(target), "not present") + raise + + def verifyElementNotPresent(self, driver, target, value="", url=""): + """ + Verify the absence of a specified web element on a web page. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element to be verified for absence. + """ + present = True + try: + find_element(driver, target) + except NoSuchElementException: + present = False + + try: + assert not present + except Exception: + print("verifyElementNotPresent: ", repr(target), "present") + raise + + def waitForTextPresent(self, driver, text, target="", value="", url=""): + """ + Wait for the specified text to be present in the page source. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + text (str): The text to wait for in the page source. + """ + try: + assert bool(text in driver.page_source) + except Exception: + print("waitForTextPresent: ", repr(text), "not present") + raise + + def waitForTextNotPresent(self, driver, text, target="", value="", url=""): + """ + Wait for the specified text to be absent in the page source. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + text (str): The text to wait for to be absent in the page source. + """ + try: + assert not bool(text in driver.page_source) + except Exception: + print("waitForTextNotPresent: ", repr(text), "present") + raise + + def assertSimple(self, driver, target, value="", url=""): + target_value = find_element(driver, target).get_attribute("value") + assert(target_value == value) + + def assertText(self, driver, target, value="", url=""): + """ + Assert that the text of a specified web element matches the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose text needs to be asserted. + value (str, optional): The expected text value to compare against (default is an empty string). + """ + try: + target_value = find_element(driver, target).text + print(" assertText target value =" + repr(target_value)) + if value.startswith("exact:"): + assert target_value == value[len("exact:") :] + else: + assert target_value == value + except Exception: + print( + "assertText: ", + repr(target), + repr(find_element(driver, target).get_attribute("value")), + repr(value), + ) + raise + + def assertNotText(self, driver, target, value="", url=""): + """ + Assert that the text of a specified web element does not match the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose text needs to be asserted. + value (str, optional): The expected text value to compare against (default is an empty string). + """ + try: + target_value = find_element(driver, target).text + print(" assertNotText target value =" + repr(target_value)) + if value.startswith("exact:"): + assert target_value != value[len("exact:") :] + else: + assert target_value != value + except Exception: + print( + "assertNotText: ", + repr(target), + repr(find_element(driver, target).get_attribute("value")), + repr(value), + ) + raise + + def assertValue(self, driver, target, value="", url=""): + """ + Assert that the value attribute of a specified web element matches the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be asserted. + value (str, optional): The expected value to compare against (default is an empty string). + """ + try: + target_value = find_element(driver, target).get_attribute("value") + print(" assertValue target value =" + repr(target_value)) + assert target_value == value + except Exception: + print( + "assertValue: ", + repr(target), + repr(find_element(driver, target).get_attribute("value")), + repr(value), + ) + raise + + def assertNotValue(self, driver, target, value="", url=""): + """ + Assert that the value attribute of a specified web element does not match the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be asserted. + value (str, optional): The expected value to compare against (default is an empty string). + """ + try: + target_value = find_element(driver, target).get_attribute("value") + print(" assertNotValue target value =" + repr(target_value)) + assert target_value != value + except Exception: + print( + "assertNotValue: ", + repr(target), + repr(target_value), + repr(value), + ) + raise + + def verifyValue(self, driver, target, value="", url=""): + """ + Verify that the value attribute of a specified web element matches the expected value. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be verified. + value (str, optional): The expected value to compare against (default is an empty string). + """ + try: + target_value = find_element(driver, target).get_attribute("value") + print(" verifyValue target value =" + repr(target_value)) + assert target_value == value + except Exception: + print( + "verifyValue: ", + repr(target), + repr(find_element(driver, target).get_attribute("value")), + repr(value), + ) + raise + + def mouseOver(self, driver, target, value="", url=""): + """ + Simulate a mouse over event on the specified target element. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be verified. + """ + element = find_element(driver, target) + driver.execute_script( + "arguments[0].dispatchEvent(new Event('mouseover'))", element + ) + + def mouseOut(self, driver, target, value="", url=""): + """ + Simulate a mouse out event on the specified target element. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be verified. + """ + element = find_element(driver, target) + driver.execute_script( + "arguments[0].dispatchEvent(new Event('mouseout'))", element + ) + + def sendKeys(self, driver, target, value="", url=""): + """ + Send the specified text to the target element. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + target (str): The identifier for the web element whose value attribute needs to be verified. + value (str, optional): The text to send to the target element. + """ + element = find_element(driver, target) + element.send_keys(value) + + def executeScript(self, driver, target, value="", url=""): + """ + Execute a script script in the browser. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + script (str): The script code to execute. + """ + driver.execute_script(target) + + def waitForElementVisible(self, driver, target, value="", url=""): + element = find_element(driver, target) + WebDriverWait(driver, 30).until(EC.visibility_of_element_located(element)) + + +class SeleniumTestSuite(object): + """A Selenium Test Suite""" + + def __init__(self, filename, callback=None): + """ + Initialize a SeleniumTestSuite instance. + + Args: + filename (str): The name of the test suite JSON file. + callback (callable, optional): A callback function to be executed after each test (default is None). + """ + self.filename = filename + self.callback = callback + + self.tests = [] + + with open(filename, "r") as json_file: + json_data = json.load(json_file) + self.title = json_data.get("name", "Untitled Suite") + self.tests = [ + (test_data.get("name", "Untitled Test"), test_data) + for test_data in json_data.get("tests", []) + ] + + def run(self, driver, url): + """ + Run the Selenium test suite with the specified WebDriver instance and URL. + + Args: + driver (WebDriver): The WebDriver instance for interacting with the web page. + url (str): The base URL to be used for the tests. + """ + for test_title, test_data in self.tests: + try: + test = SeleniumTestCase(test_data, self.callback) + test.run(driver, url) + except Exception as e: + print(f"Error in {test_title} ({test.filename}): {e}") + raise + + def __repr__(self): + """ + Return a string representation of the SeleniumTestSuite. + + Returns: + str: A string representation of the test suite, including its title and test titles. + """ + test_titles = "\n".join([title for title, _ in self.tests]) + return f"{self.title}\n{test_titles}" + + +class PodSeleniumTests(StaticLiveServerTestCase): + """Tests the integration of Pod application with Selenium from side files""" + + fixtures = ["initial_data.json"] + + def setUp(self): + """Set up the tests and initialize custom test data.""" + self.initialize_data() + + @classmethod + def setUpClass(cls): + """Create the WebDriver for all Selenium tests.""" + super().setUpClass() + cls.selenium = WebDriver() + cls.selenium.implicitly_wait(10) + + @classmethod + def tearDownClass(cls): + """Close the WebDriver used.""" + cls.selenium.quit() + super().tearDownClass() + + @override_settings(DEBUG=False) + def test_selenium_suites(self): + """Run Selenium Test Suites from Side files in all installed apps.""" + self.selenium.get(f"{self.live_server_url}/") + self.run_initial_tests() + time.sleep(1) + self.run_all_simple_tests() + time.sleep(1) + self.run_initial_staff_tests() + time.sleep(1) + self.run_all_staff_tests() + + def initialize_data(self): + """Initialize custom test data.""" + self.user_simple = User.objects.create_user(username="user", password="user") + self.user_staff = User.objects.create_user(username="staff_user", password="user") + self.user_staff.is_staff = True + self.user_staff.save() + # copy video file + + self.video = Video.objects.create( + title="first-video", + owner=self.user_simple, + is_draft=False, + type=Type.objects.get(id=1), + ) + tempfile = NamedTemporaryFile(delete=True) + self.video.video.save("test.mp4", tempfile) + dest = os.path.join(settings.MEDIA_ROOT, self.video.video.name) + shutil.copyfile("pod/main/static/video_test/pod.mp4", dest) + self.video_staff = Video.objects.create( + title="video-staff", + owner=self.user_staff, + is_draft=False, + type=Type.objects.get(id=1), + ) + tempfile = NamedTemporaryFile(delete=True) + self.video_staff.video.save("test.mp4", tempfile) + dest = os.path.join(settings.MEDIA_ROOT, self.video_staff.video.name) + shutil.copyfile("pod/main/static/video_test/pod.mp4", dest) + + encode_video(self.video.id) + encode_video(self.video_staff.id) + + def run_suite(self, suite_name, suite_url): + """ + Run a Selenium test suite with the specified name and URL. + + Args: + suite_name (str): The name of the test suite JSON file. + suite_url (str): The base URL for the test suite. + """ + global current_side_file + print(f"Running test suite: {suite_name}") + + with open(suite_name, "r") as json_file: + suite_data = json.load(json_file) + suite_tests = suite_data.get("tests", []) + + for test_data in suite_tests: + test_case = SeleniumTestCase(test_data) + try: + test_case.run(PodSeleniumTests.selenium, suite_url) + except Exception: + PodSeleniumTests.selenium.save_screenshot( + f"{suite_name}-error_screen.png" + ) + current_side_file = suite_name + self.fail(f"Error in suite {suite_name}") + + def run_initial_tests(self): + """Run initial Selenium test suites for cookies and login.""" + initial_tests_dir = os.path.join( + os.path.dirname(__file__), "init_integration_tests") + cookies_test_path = os.path.join(initial_tests_dir, "cookies.side") + login_test_path = os.path.join(initial_tests_dir, "connexion.side") + self.run_single_suite(cookies_test_path) + self.run_single_suite(login_test_path) + + def run_initial_staff_tests(self): + """Run initial Selenium test suites for cookies and login.""" + initial_tests_dir = os.path.join( + os.path.dirname(__file__), "init_integration_tests") + deconnexion_path = os.path.join(initial_tests_dir, "deconnexion.side") + login_test_path = os.path.join(initial_tests_dir, "connexion_staff.side") + self.run_single_suite(deconnexion_path) + self.run_single_suite(login_test_path) + + def run_all_simple_tests(self): + """ + Run Selenium test suites in all installed apps. + + This method searches for test suites in integration_tests/tests directories of all installed apps and runs them. + """ + for app in settings.INSTALLED_APPS: + app_module = __import__(app, fromlist=["integration_tests"]) + integration_tests_dir = os.path.join( + os.path.dirname(app_module.__file__), "integration_tests" + ) + if os.path.exists(integration_tests_dir): + tests_dir = os.path.join(integration_tests_dir, "tests") + if os.path.exists(tests_dir): + print(f"Running Selenium tests in {app}...") + self.run_tests_in_directory(tests_dir, suffixe = "_simple") + print("All simple tests are DONE") + + def run_all_staff_tests(self): + """ + Run Selenium test suites in all installed apps. + + This method searches for test suites in integration_tests/tests directories of all installed apps and runs them. + """ + for app in settings.INSTALLED_APPS: + app_module = __import__(app, fromlist=["integration_tests"]) + integration_tests_dir = os.path.join( + os.path.dirname(app_module.__file__), "integration_tests" + ) + if os.path.exists(integration_tests_dir): + tests_dir = os.path.join(integration_tests_dir, "tests") + if os.path.exists(tests_dir): + print(f"Running Selenium tests in {app}...") + self.run_tests_in_directory(tests_dir, suffixe = "_staff") + print("All simple tests are DONE") + + def run_tests_in_directory(self, directory, suffixe = ""): + """ + Run Selenium test suites in the specified directory. + + Args: + directory (str): The directory containing test suites. + """ + for root, dirs, files in os.walk(directory): + for filename in files: + if filename.endswith("%s.side" % suffixe): + suite_name = os.path.join(root, filename) + self.run_single_suite(suite_name) + + def run_single_suite(self, suite_name): + """ + Run a single Selenium test suite. + + Args: + suite_name (str): The name of the test suite JSON file to run. + """ + try: + suite_url = self.live_server_url + self.run_suite(suite_name, suite_url) + PodSeleniumTests.selenium.save_screenshot(f"{suite_name}-info_screen.png") + except Exception: + PodSeleniumTests.selenium.save_screenshot(f"{suite_name}-error_screen.png") + print("ERROR !") + print(f"Side path : {current_side_file}") + self.fail(f"Error in suite {suite_name}") diff --git a/pod/main/integration_tests/tests/search_simple.side b/pod/main/integration_tests/tests/search_simple.side index ede54b955d..ffdea4bc15 100644 --- a/pod/main/integration_tests/tests/search_simple.side +++ b/pod/main/integration_tests/tests/search_simple.side @@ -2,91 +2,81 @@ "id": "f060a750-1757-4b5d-8ed0-ce6b1c49b62b", "version": "2.0", "name": "pod-front", - "url": "http://localhost:9090/", - "tests": [ - { - "id": "pod-searchbar", - "name": "search", - "commands": [ - { - "id": "pod-open-home", - "comment": "Ouvre la page d'accueil", - "command": "open", - "target": "/", - "targets": [], - "value": "" - }, - { - "id": "pod-search", - "comment": "Sélectionne barre recherche", - "command": "click", - "target": "id=s", - "targets": [ - ["id=s", "id"], - ["name=q", "name"], - ["css=#s", "css:finder"], - ["xpath=//input[@id='s']", "xpath:attributes"], - ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], - ["xpath=//input", "xpath:position"] - ], - "value": "" - }, - { - "id": "pod-search-type", - "comment": "Tape mot clé", - "command": "type", - "target": "id=s", - "targets": [ - ["id=s", "id"], - ["name=q", "name"], - ["css=#s", "css:finder"], - ["xpath=//input[@id='s']", "xpath:attributes"], - ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], - ["xpath=//input", "xpath:position"] - ], - "value": "pas de résultat attendu" - }, - { - "id": "pod-search-enter", - "comment": "Appuie touche entrée", - "command": "sendKeys", - "target": "id=s", - "targets": [ - ["id=s", "id"], - ["name=q", "name"], - ["css=#s", "css:finder"], - ["xpath=//input[@id='s']", "xpath:attributes"], - ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], - ["xpath=//input", "xpath:position"] - ], - "value": "${KEY_ENTER}" - }, - { - "id": "pod-accueil-ariane", - "comment": "Retour à la page d'accueil via logo pod", - "command": "click", - "target": "xpath=//div[@id='nav-mainbar']/a/strong", - "targets": [ - ["css=strong", "css:finder"], - ["xpath=//div[@id='nav-mainbar']/a/strong", "xpath:idRelative"], - ["xpath=//strong", "xpath:position"], - ["xpath=//strong[contains(.,'Lille.Pod')]", "xpath:innerText"] - ], - "value": "" - } - ] - } - ], - "suites": [ - { - "id": "pod-front", - "name": "Esup Pod test selenium", - "persistSession": false, - "parallel": false, - "timeout": 300, - "tests": ["pod-searchbar"] - } - ], + "url": "http://localhost:9090", + "tests": [{ + "id": "pod-searchbar", + "name": "search", + "commands": [{ + "id": "pod-open-home", + "comment": "Ouvre la page d'accueil", + "command": "open", + "target": "/", + "targets": [], + "value": "" + }, { + "id": "pod-search", + "comment": "Sélectionne barre recherche", + "command": "click", + "target": "id=s", + "targets": [ + ["id=s", "id"], + ["name=q", "name"], + ["css=#s", "css:finder"], + ["xpath=//input[@id='s']", "xpath:attributes"], + ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, { + "id": "pod-search-type", + "comment": "Tape mot clé", + "command": "type", + "target": "id=s", + "targets": [ + ["id=s", "id"], + ["name=q", "name"], + ["css=#s", "css:finder"], + ["xpath=//input[@id='s']", "xpath:attributes"], + ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "pas de résultat attendu" + }, { + "id": "pod-search-enter", + "comment": "Appuie touche entrée", + "command": "sendKeys", + "target": "id=s", + "targets": [ + ["id=s", "id"], + ["name=q", "name"], + ["css=#s", "css:finder"], + ["xpath=//input[@id='s']", "xpath:attributes"], + ["xpath=//form[@id='nav-search']/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "pod-accueil-ariane", + "comment": "Retour à la page d'accueil via logo pod", + "command": "click", + "target": "xpath=//div[@id='nav-mainbar']/a/strong", + "targets": [ + ["css=strong", "css:finder"], + ["xpath=//div[@id='nav-mainbar']/a/strong", "xpath:idRelative"], + ["xpath=//strong", "xpath:position"], + ["xpath=//strong[contains(.,'Lille.Pod')]", "xpath:innerText"] + ], + "value": "" + }] + }], + "suites": [{ + "id": "pod-front", + "name": "Esup Pod test selenium", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["pod-searchbar"] + }], "urls": ["http://localhost:9090/"], "plugins": [] -} +} \ No newline at end of file diff --git a/pod/main/integration_tests/tests/side_anonyme_test_lang.py b/pod/main/integration_tests/tests/side_anonyme_test_lang.py new file mode 100644 index 0000000000..9b6a3dbdda --- /dev/null +++ b/pod/main/integration_tests/tests/side_anonyme_test_lang.py @@ -0,0 +1,34 @@ +# Generated by Selenium IDE +import pytest +import time +import json +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +class TestLang(): + def setup_method(self, method): + self.driver = webdriver.Firefox() + self.vars = {} + + def teardown_method(self, method): + self.driver.quit() + + def test_lang(self): + self.driver.get("http://localhost:9090//") + self.driver.find_element(By.ID, "pod-param-buttons__button").click() + element = self.driver.find_element(By.ID, "pod-lang-select") + actions = ActionChains(self.driver) + # actions.move_to_element(element).perform() + self.driver.execute_script("arguments[0].dispatchEvent(new Event('mouseover'));", element) + self.driver.find_element(By.ID, "pod-lang-select").click() + element = self.driver.find_element(By.CSS_SELECTOR, "body") + actions = ActionChains(self.driver) + # actions.move_to_element(element, 0, 0).perform() + self.driver.execute_script("arguments[0].dispatchEvent(new Event('mouseover'));", element) + self.driver.find_element(By.CSS_SELECTOR, ".dropdown-item").click() + diff --git a/pod/main/integration_tests/tests/side_anonyme_test_search.py b/pod/main/integration_tests/tests/side_anonyme_test_search.py new file mode 100644 index 0000000000..09d8cefd6c --- /dev/null +++ b/pod/main/integration_tests/tests/side_anonyme_test_search.py @@ -0,0 +1,27 @@ +# Generated by Selenium IDE +import pytest +import time +import json +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +class TestSearch(): + def setup_method(self, method): + self.driver = webdriver.Firefox() + self.vars = {} + + def teardown_method(self, method): + self.driver.quit() + + def test_search(self): + self.driver.get("http://localhost:9090/") + self.driver.find_element(By.ID, "s").click() + self.driver.find_element(By.ID, "s").send_keys("pas de résultat attendu") + self.driver.find_element(By.ID, "s").send_keys(Keys.ENTER) + self.driver.find_element(By.XPATH, "//div[@id=\'nav-mainbar\']/a/strong").click() +