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