Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .dockerignore
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Для информации (исправлять не нужно) - для разных образов (точнее Dockerfile'ов) можно указывать свои .dockerignore (ссылка)

Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ venv
__pycache__
Dockerfile*
app/playground
tests/selenium
7 changes: 4 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ jobs:

- name: Build docker-compose
run: |
docker compose build
docker compose -f docker-compose.yml -f docker-compose-selenium.yml build

- name: Run docker-compose
run:
APP_CONF=../app_conf/testing.ini docker compose up -d
APP_CONF=../app_conf/testing.ini docker compose -f docker-compose.yml -f docker-compose-selenium.yml up -d

- name: Run tests
run: |
docker ps -a
docker compose logs
docker exec web_speech_trainer-web-1 bash -c 'cd /project/tests && pytest .'
docker exec web_speech_trainer-web-1 bash -c 'cd /project/tests && pytest --ignore=selenium'
docker exec web_speech_trainer-selenium-tests-1 bash -c 'pytest .'
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.venv
venv
.venv
.idea
ssl
__pycache__
Expand Down
22 changes: 22 additions & 0 deletions Dockerfile_selenium
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM selenium/standalone-chrome:137.0-chromedriver-137.0-grid-4.33.0-20250606

WORKDIR /usr/src/project

USER root
RUN apt-get update && \
apt-get install -y python3 python3-pip && \
rm -rf /var/lib/apt/lists/*

COPY tests/requirements.txt requirements.txt
RUN pip install -r requirements.txt

COPY tests/selenium selenium
COPY tests/test_data test_data
COPY tests/simple_phrases_russian.wav simple_phrases_russian.wav

COPY app/config.py app/config.py
COPY app_conf/testing.ini app_conf/testing.ini

ENV PYTHONPATH='/usr/src/project/:/usr/src/project/app/'

# CMD ["pytest", "-s", "."]
17 changes: 0 additions & 17 deletions Dockerfile_test

This file was deleted.

1 change: 1 addition & 0 deletions app/routes/lti.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def lti():
an empty dictionary with 404 HTTP return code if access was denied.
"""
params = request.form

consumer_key = params.get('oauth_consumer_key', '')
consumer_secret = ConsumersDBManager().get_secret(consumer_key)
request_info = dict(
Expand Down
6 changes: 3 additions & 3 deletions app/static/js/recording.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ let gumStream,
timer;

function startRecording() {
console.log("call startRecording(). Try to call navigator.mediaDevices.getUserMedia")
console.log(navigator)
console.log(navigator.mediaDevices)
console.log("call startRecording(). Try to call navigator.mediaDevices.getUserMedia");
console.log("navigator", navigator);
console.log("navigator.mediaDevices", navigator.mediaDevices);
$("#alert").hide()
$("#record-contain").show();
navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(function (stream) {
Expand Down
12 changes: 12 additions & 0 deletions docker-compose-selenium.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: '2'

services:
selenium-tests:
build:
context: .
dockerfile: Dockerfile_selenium
shm_size: 2g
depends_on:
- web
networks:
- default
3 changes: 1 addition & 2 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
pytest==8.0.2
selenium==4.16.0
webdriver-manager==4.0.1
selenium==4.33.0
117 changes: 73 additions & 44 deletions tests/selenium/test_training.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Организуйте запуск тест-кейсов аналогично примеру - в дальнейшем у нас будут появляться новые тестовые сценарии, для которых потребуется централизованный запуск (хотя сейчас это и будет только один)
  2. Вынесите базовую логику теста в отдельный класс
    • так будет проще реализовывать следующие тесты
    • "TestBasicTraining" по факту уже не "basic", а конкретный TestTraining

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Залил коммит. Запуск тест кейсов как в примере, наверное, характерен для unittest.
Я сделал 3 файла:

  • selenium_session - базовая логика для любых будущих селениум тестов
  • training_session - базовая логика для тестирования тренировок, основанная на selenium_session, возможно избыточно
  • test_simple_training - тест, с которого все началось

Мне кажется, для новых тестов будет проще скопировать test_simple_training и использовать SeleniumSession или TrainingSession

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Просто main.py в случае pytest как будто бы избыточен, я могу ошибаться

Copy link
Collaborator Author

@PeeachPie PeeachPie Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделал немного мудрее. Теперь Training это просто обертка на SeleniumSession использующая экземпляр последнего. Соотвественно обертки можно менять по ходу тестирования. Более того тесты могут наследоваться, что поможет сформировать четкую иерархию

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import requests
from time import sleep

from selenium.common.exceptions import TimeoutException
Expand All @@ -7,64 +8,92 @@
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from seleniumrequests import Chrome
from selenium.webdriver import Chrome

from app.config import Config


def test_basic_training():
Config.init_config('../app_conf/testing.ini')
class TestBasicTraining:
Config.init_config('/usr/src/project/app_conf/testing.ini')

chrome_options = Options()

chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--headless')
chrome_options.add_argument('--unsafely-treat-insecure-origin-as-secure=http://web:5000')

chrome_options.add_argument("--disable-user-media-security")
chrome_options.add_argument("--use-fake-ui-for-media-stream")
chrome_options.add_argument("--use-fake-device-for-media-stream")
chrome_options.add_argument("--use-fake-ui-for-media-stream")
chrome_options.add_argument('--use-file-for-fake-audio-capture={}/simple_phrases_russian.wav'.format(os.getcwd()))

chrome_options.add_experimental_option('detach', True)

driver = Chrome(options=chrome_options)
driver.request('POST', 'http://127.0.0.1:5000/lti', data={
'lis_person_name_full': Config.c.testing.lis_person_name_full,
'ext_user_username': Config.c.testing.session_id,
'custom_task_id': Config.c.testing.custom_task_id,
'custom_task_description': Config.c.testing.custom_task_description,
'custom_attempt_count': Config.c.testing.custom_attempt_count,
'custom_required_points': Config.c.testing.custom_required_points,
'custom_criteria_pack_id': Config.c.testing.custom_criteria_pack_id,
'roles': Config.c.testing.roles,
'lis_outcome_service_url': Config.c.testing.lis_outcome_service_url,
'lis_result_sourcedid': Config.c.testing.lis_result_source_did,
'oauth_consumer_key': Config.c.testing.oauth_consumer_key,
})
driver.get('http://127.0.0.1:5000/upload_presentation/')
file_input = WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "input[type=file]")))
file_input.send_keys(f'{os.getcwd()}/test_data/test_presentation_file_0.pdf')
WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.ID, "button-submit"))).click()
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "record"))).click()
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "model-timer")))
WebDriverWait(driver, 10).until(EC.invisibility_of_element((By.ID, "model-timer")))
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "next")))
sleep(5)
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "next"))).click()
session = requests.Session()
sleep(5)
WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.ID, "done"))).click()
alert = Alert(driver)
alert.accept()

feedback_flag = False
step_count = 10
step = 10
for _ in range(step_count):
driver.refresh()
try:
feedback_element = WebDriverWait(driver, step).until(EC.presence_of_element_located((By.ID, 'feedback')))
if feedback_element.text.startswith('Оценка за тренировку'):

driver.get('http://web:5000/init/')

def test_registration(self):
self.session.request('POST','http://web:5000/lti', data={
'lis_person_name_full': Config.c.testing.lis_person_name_full,
'ext_user_username': Config.c.testing.session_id,
'custom_task_id': Config.c.testing.custom_task_id,
'custom_task_description': Config.c.testing.custom_task_description,
'custom_attempt_count': Config.c.testing.custom_attempt_count,
'custom_required_points': Config.c.testing.custom_required_points,
'custom_criteria_pack_id': Config.c.testing.custom_criteria_pack_id,
'roles': Config.c.testing.roles,
'lis_outcome_service_url': Config.c.testing.lis_outcome_service_url,
'lis_result_sourcedid': Config.c.testing.lis_result_source_did,
'oauth_consumer_key': Config.c.testing.oauth_consumer_key,
})

def test_presentation_upload(self):
self.driver.get('http://web:5000/upload_presentation/')

file_input = WebDriverWait(self.driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "input[type=file]")))
file_input.send_keys(f'{os.getcwd()}/test_data/test_presentation_file_0.pdf')

WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.ID, "button-submit"))).click()

def test_record_preparation(self):
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID, "record"))).click()

WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "model-timer")))

WebDriverWait(self.driver, 10).until(EC.invisibility_of_element((By.ID, "model-timer")))

sleep(5)

def test_button_next(self):
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID, "next"))).click()

sleep(5)

def test_training_session_end(self):
WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.ID, "done"))).click()

WebDriverWait(self.driver, 5).until(lambda d : d.switch_to.alert).accept()

sleep(5)

def test_training_feedback(self):
feedback_flag = False
step_count = 10
step = 10

for _ in range(step_count):
self.driver.refresh()

feedback_elements = self.driver.find_elements(By.ID, 'feedback')

if feedback_elements and feedback_elements[0].text.startswith('Оценка за тренировку'):
feedback_flag = True
break

sleep(step)
except:
sleep(step)
driver.close()
assert feedback_flag, f"Проверка тренировки заняла более {step_count*step} секунд"

self.driver.close()

assert feedback_flag, f"Проверка тренировки заняла более {step_count * step} секунд"
15 changes: 0 additions & 15 deletions tests/selenium/testing.ini

This file was deleted.

Binary file modified tests/simple_phrases_russian.wav
Binary file not shown.
Binary file added tests/simple_phrases_russian_old.wav
Binary file not shown.