diff --git a/apps/content/tests/mixins.py b/apps/content/tests/mixins.py index 71ff7e6e..4fca8e65 100644 --- a/apps/content/tests/mixins.py +++ b/apps/content/tests/mixins.py @@ -3,6 +3,7 @@ import time from factory.faker import Faker from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By +from django.utils.html import strip_tags class TestContentMixin: @@ -22,9 +23,10 @@ class TestContentMixin: for i, item in enumerate(object.content.all().order_by('position', '-created_at',)): item_data = self.content_data[i].get('data') for key, value in item_data.items(): - print('real obj field value:', getattr(item, key, None)) - print('expected value:', value) - self.assertEqual(getattr(item, key), value) + if key == 'txt': + self.assertEqual(strip_tags(getattr(item, key)), value) + else: + self.assertEqual(getattr(item, key), value) print('OK') def check_content(self, inside_el=None): @@ -34,7 +36,7 @@ class TestContentMixin: open_el = self.wait_elem_name('block-add-open', block_add_el) self.assertRaises(TimeoutException, lambda: self.wait_elem_name('block-add-close', block_add_el)) open_el.click() - time.sleep(1) + time.sleep(0.5) close_el = self.wait_elem_name('block-add-close', block_add_el) block_text_el = self.wait_elem_name('block-add-block-text', block_add_el) @@ -42,21 +44,29 @@ class TestContentMixin: time.sleep(0.5) self.check_block_text(inside_el) + open_el.click() + time.sleep(0.5) block_image_el = self.wait_elem_name('block-add-block-image', block_add_el) block_image_el.click() time.sleep(0.5) self.check_block_image(inside_el) + open_el.click() + time.sleep(0.5) block_image_text_el = self.wait_elem_name('block-add-block-image-text', block_add_el) block_image_text_el.click() time.sleep(0.5) self.check_block_image_text(inside_el) + open_el.click() + time.sleep(0.5) block_images_el = self.wait_elem_name('block-add-block-images', block_add_el) block_images_el.click() time.sleep(0.5) self.check_block_images(inside_el) + open_el.click() + time.sleep(0.5) block_video_el = self.wait_elem_name('block-add-block-video', block_add_el) block_video_el.click() time.sleep(0.5) @@ -68,8 +78,7 @@ class TestContentMixin: block_obj = {'type': 'text', 'data': {}} block_el = self.wait_elem_name('block-text', inside_el) title_el = self.wait_elem_name('block-text-title', block_el) - text_el = self.wait_elem_name('block-text-text-wrap', block_el) - text_el.find_element( + text_el = self.wait_elem_name('block-text-text-wrap', block_el).find_element( By.XPATH, './/div[contains(@class, "redactor-layer")][@contenteditable]') self.content_data.append(block_obj) @@ -77,11 +86,9 @@ class TestContentMixin: title_el.send_keys(title) block_obj['data']['title'] = title text = Faker('sentence', nb_words=50).generate({}) - self.driver.execute_script("arguments[0].click();", text_el) - self.driver.execute_script("arguments[0].innerHtml = arguments[1];", text_el, text) - self.driver.execute_script("arguments[0].dispatchEvent(new Event('change'));", text_el) - # text_el.send_keys(text) - block_obj['data']['text'] = '

%s

' % text + text_el.click() + text_el.send_keys(text) + block_obj['data']['txt'] = text self.check_auto_saving() def check_block_image(self, inside_el=None): @@ -114,11 +121,9 @@ class TestContentMixin: title_el.send_keys(title) block_obj['data']['title'] = title text = Faker('sentence', nb_words=50).generate({}) - self.driver.execute_script("arguments[0].click();", text_el) - self.driver.execute_script("arguments[0].innerHtml = arguments[1];", text_el, text) - self.driver.execute_script("arguments[0].dispatchEvent(new Event('change'));", text_el) - # text_el.send_keys(text) - block_obj['data']['text'] = '

%s

' % text + text_el.click() + text_el.send_keys(text) + block_obj['data']['txt'] = text # TODO: check image upload self.check_auto_saving() diff --git a/apps/course/tests/test_views.py b/apps/course/tests/test_views.py index 9ddcc30c..e86b058f 100644 --- a/apps/course/tests/test_views.py +++ b/apps/course/tests/test_views.py @@ -1,8 +1,8 @@ from datetime import timedelta from random import randint import time +import re -from selenium import webdriver from selenium.common.exceptions import TimeoutException from factory.faker import Faker from django.utils import timezone @@ -70,9 +70,11 @@ class CourseEditTestCase(TestContentMixin, SeleniumTestCase): super().tearDownClass() def check_auto_saving(self): - # TODO - time.sleep(5) + time.sleep(0.5) + request = self.wait_for_request(f'/api/v1/courses/{self.object_id}/') obj = self.model.objects.get(id=self.object_id) + self.assertEqual(request[2].get('status'), 200) + self.assertNotEqual(request[2].get('response'), None) for key, value in self.object_data.items(): if key != 'content': self.assertEqual(getattr(obj, key), value) @@ -85,6 +87,8 @@ class CourseEditTestCase(TestContentMixin, SeleniumTestCase): print('url is', url) self.driver.get(url) print('page opened', self.driver.current_url) + self.add_requests_log() + course_redactor = self.wait_elem_name('course-redactor') # visible always elements title_el = self.wait_elem_name('course-title') @@ -106,11 +110,11 @@ class CourseEditTestCase(TestContentMixin, SeleniumTestCase): title = Faker('sentence', nb_words=6).generate({}) title_el.send_keys(title) slug = slugify(unidecode(title[:90])) + slug = re.sub(r'[^-\w]+$', '', slug) + slug = re.sub(r'[^-\w]', '-', slug) self.object_data['title'] = title self.object_data['slug'] = slug - # print('title:', title) - # print('slug:', slug) - time.sleep(2) + time.sleep(1) self.assertEqual(slug_el.get_attribute('value'), slug) # check save @@ -143,7 +147,7 @@ class CourseEditTestCase(TestContentMixin, SeleniumTestCase): time_el = self.wait_elem_name('course-time') except: self.fail('Date and time elements not shown') - + self.check_content(content_el) # lessons_el = self.wait_elem_name('course-lessons') @@ -151,4 +155,3 @@ class CourseEditTestCase(TestContentMixin, SeleniumTestCase): # lesson_edit_el = self.wait_elem_name('course-lesson-edit') # stream_el = self.wait_elem_name('course-stream') - diff --git a/project/tests/__init__.py b/project/tests/__init__.py index 41f2bc52..0bf7f1ed 100644 --- a/project/tests/__init__.py +++ b/project/tests/__init__.py @@ -3,19 +3,21 @@ from selenium import webdriver from selenium.webdriver.common.by import By from pyvirtualdisplay import Display from django.conf import settings +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC from project.utils.selenium_utils import SeleniumExtensions as SE from apps.auth.models import TempToken -class SeleniumTestCase(LiveServerTestCase ): +class SeleniumTestCase(LiveServerTestCase): @classmethod def setUpClass(cls): super().setUpClass() cls.display = Display(visible=0, size=(1280, 1024)) cls.display.start() - cls.driver = webdriver.Chrome() + cls.driver = webdriver.Firefox() @classmethod def tearDownClass(cls): @@ -48,6 +50,16 @@ class SeleniumTestCase(LiveServerTestCase ): return SE.wait_elems(self.driver, (By.NAME, name), inside_el, wait_time) def login(self, user): + TempToken.objects.all().delete() tt = TempToken.objects.create(user=user) self.driver.get('%s?temp-token=%s' % (self.get_url(), tt.key)) + def add_requests_log(self): + SE.add_requests_log(self.driver) + + def get_requests(self): + return SE.get_requests(self.driver) + + def wait_for_request(self, path, wait_time=10): + return SE.wait_for_request(self.driver, path, wait_time) + diff --git a/project/utils/selenium_utils.py b/project/utils/selenium_utils.py index 4f523d1b..3ad4ab17 100644 --- a/project/utils/selenium_utils.py +++ b/project/utils/selenium_utils.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from selenium import webdriver -from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC @@ -44,3 +42,52 @@ class SeleniumExtensions(object): return WebDriverWait(driver, wait_time).until( EC.presence_of_all_elements_located(locator) ) + + @classmethod + def wait_for_js_load(cls, driver, wait_time=10): + WebDriverWait(driver, wait_time).until( + lambda driver: driver.execute_script('return document.readyState') == 'complete') + + @classmethod + def add_requests_log(cls, driver): + js = ''' + (function() { + var open = XMLHttpRequest.prototype.open; + var send = XMLHttpRequest.prototype.send; + window._requestsLog = []; + window._findRequest = function(pathOrXHR){ + for(var i=window._requestsLog.length - 1; i >= 0; i--){ + var request = window._requestsLog[i]; + if(typeof(pathOrXHR) == 'string' && request[1].indexOf(pathOrXHR) > -1 + || request[2] === pathOrXHR){ + return request; + } + } + } + XMLHttpRequest.prototype.open = function(method, url){ + window._requestsLog.push([method, url, this]); + return open.apply(this, arguments); + } + XMLHttpRequest.prototype.send = function(body){ + var r = window._findRequest(this); + if(r){ + r.push(body); + } + return send.apply(this, arguments); + } + })(); + ''' + return driver.execute_script(js) + + @classmethod + def get_requests(cls, driver): + return driver.execute_script('return window._requestsLog;') + + @classmethod + def wait_for_request(cls, driver, path, wait_time=10): + WebDriverWait(driver, wait_time).until( + lambda driver: driver.execute_script(''' + var r = window._findRequest(arguments[0]); + return !!r && r[2].readyState == 4; + ''', path) is True) + return driver.execute_script('return window._findRequest(arguments[0]);', path) diff --git a/web/src/components/CourseRedactor.vue b/web/src/components/CourseRedactor.vue index 709bea46..54d61412 100644 --- a/web/src/components/CourseRedactor.vue +++ b/web/src/components/CourseRedactor.vue @@ -487,10 +487,13 @@ onCoursePriceChange(event) { this.course.price = event.target.value; }, + getSlug(text) { + return slugify(text || '').toLowerCase().replace(/[^-\w]+$/, '').replace(/[^-\w]/g, '-'); + }, onCourseNameInput() { this.$v.course.title.$touch(); if (!this.slugChanged && !this.$v.course.status) { - this.course.slug = (slugify(this.course.title) || '').toLowerCase(); + this.course.slug = this.getSlug(this.course.title); } }, removeLesson(lessonIndex) { @@ -767,7 +770,7 @@ this.courseSaving = true; this.changeSavingStatus(); const courseObject = this.course; - courseObject.slug = courseObject.slug && slugify(courseObject.slug); + courseObject.slug = this.getSlug(courseObject.slug); api.saveCourse(courseObject, this.accessToken) .then((response) => { this.courseSaving = false;