diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index bf7a7f4447..eeed609b15 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -17,10 +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+css code with prettier + # Prettify .js | .json | .side | .css files with prettier - name: Running prettier run: | npx prettier --write pod/*/static/**/*.js + npx prettier --write pod/*/static/**/*.json + npx prettier --write --parser json pod/**/*.side npx prettier --write pod/*/static/**/*.css - name: Check for modified files @@ -67,3 +69,17 @@ jobs: run: | git commit -am "Auto-update configuration files" git push + + # Compile lang files + - name: Compile lang files in fr and nl + run: make compilelang + + - name: Check for modified files + 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.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 024cafe9f5..f38fb473ee 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -7,10 +7,13 @@ on: pull_request: branches: ["*"] +permissions: + pull-requests: write + jobs: build: runs-on: ubuntu-latest - + permissions: write-all strategy: max-parallel: 4 matrix: @@ -51,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 @@ -74,6 +77,20 @@ jobs: 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 ## - name: Install pa11y-ci dependencies. @@ -106,20 +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: Check for pa11y failures. if: contains(steps.pa11y_output.outputs.content, 'Errors in http://') run: | 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/dockerfile-dev-with-volumes/pod/Dockerfile b/dockerfile-dev-with-volumes/pod/Dockerfile index a716a6ff92..32752e6d7b 100755 --- a/dockerfile-dev-with-volumes/pod/Dockerfile +++ b/dockerfile-dev-with-volumes/pod/Dockerfile @@ -26,6 +26,17 @@ RUN apt-get clean && apt-get update \ imagemagick \ gettext +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/completion/integration_tests/tests/subtitles_staff.side b/pod/completion/integration_tests/tests/subtitles_staff.side new file mode 100644 index 0000000000..03d384d8a9 --- /dev/null +++ b/pod/completion/integration_tests/tests/subtitles_staff.side @@ -0,0 +1,185 @@ +{ + "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/0002-video-staff/", + "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": "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", + "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"; } diff --git a/pod/main/configuration.json b/pod/main/configuration.json index c5f646b417..8bdc635b4e 100644 --- a/pod/main/configuration.json +++ b/pod/main/configuration.json @@ -4034,7 +4034,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.
", @@ -4045,6 +4047,23 @@ "pod_version_end": "", "pod_version_init": "3.1" }, + "USE_DEBUG_TOOLBAR": { + "default_value": false, + "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/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/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/init_integration_tests/connexion.side b/pod/main/integration_tests/init_integration_tests/connexion.side new file mode 100644 index 0000000000..b3d91f8c9f --- /dev/null +++ b/pod/main/integration_tests/init_integration_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": "/", + "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": [] +} 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/cookies.side b/pod/main/integration_tests/init_integration_tests/cookies.side new file mode 100644 index 0000000000..ca4357d1ab --- /dev/null +++ b/pod/main/integration_tests/init_integration_tests/cookies.side @@ -0,0 +1,34 @@ +{ + "id": "f060a750-1757-4b5d-8ed0-ce6b1c49b62b", + "version": "2.0", + "name": "pod-front", + "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/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/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 new file mode 100644 index 0000000000..d4e9f128a1 --- /dev/null +++ b/pod/main/integration_tests/selenium_pod_integration_tests.py @@ -0,0 +1,209 @@ +"""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 importlib +import os +import shutil + +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.webdriver.firefox.webdriver import WebDriver + +from pod.video.models import Video, Type + + +target_cache = {} +current_side_file = None + + +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.driver = WebDriver() + cls.vars = {} + cls.driver.implicitly_wait(10) + + @classmethod + def tearDownClass(cls): + """Close the WebDriver used.""" + 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.driver.get(f"{self.live_server_url}/") + # run initial test + self.run_initial_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", + is_staff=True + ) + # 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, prefixe=""): + """ + Run a Selenium test suite with the specified name and URL. + + Args: + 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}") + + 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, "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_tests(self, type): + """ + 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 {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, 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: + prefixe = "side_%s_" % type + if filename.startswith(prefixe): + suite_name = os.path.join(root, filename) + self.run_single_suite(suite_name, prefixe) + + def run_single_suite(self, suite_name, suffixe): + """ + Run a single Selenium test suite. + + Args: + suite_name (str): The name of the test suite python file to run. + """ + try: + self.run_suite(suite_name, suffixe) + PodSeleniumTests.driver.save_screenshot(f"{suite_name}-info_screen.png") + except Exception: + 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/lang_simple.side b/pod/main/integration_tests/tests/lang_simple.side new file mode 100644 index 0000000000..7f0cfc7054 --- /dev/null +++ b/pod/main/integration_tests/tests/lang_simple.side @@ -0,0 +1,118 @@ +{ + "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/tests/search_simple.side b/pod/main/integration_tests/tests/search_simple.side new file mode 100644 index 0000000000..ffdea4bc15 --- /dev/null +++ b/pod/main/integration_tests/tests/search_simple.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": [] +} \ 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() + 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 a960aa9ba5..9d5dbaf81b 100644 --- a/pod/main/test_settings.py +++ b/pod/main/test_settings.py @@ -70,6 +70,8 @@ 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 bb1a0447f3..0e8cd1145d 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: +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_simple.side b/pod/video/integration_tests/tests/comment_simple.side new file mode 100644 index 0000000000..a1819d890c --- /dev/null +++ b/pod/video/integration_tests/tests/comment_simple.side @@ -0,0 +1,195 @@ +{ + "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/0001-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/"], + "plugins": [] +} 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