diff --git a/PyRIGS/tests/base.py b/PyRIGS/tests/base.py new file mode 100644 index 00000000..7830df0f --- /dev/null +++ b/PyRIGS/tests/base.py @@ -0,0 +1,37 @@ +from django.test import LiveServerTestCase +from selenium import webdriver +from RIGS import models as rigsmodels +from . import pages +import os + + +def create_browser(): + options = webdriver.ChromeOptions() + options.add_argument("--window-size=1920,1080") + if os.environ.get('CI', False): + options.add_argument("--headless") + options.add_argument("--no-sandbox") + driver = webdriver.Chrome(chrome_options=options) + return driver + + +class BaseTest(LiveServerTestCase): + @classmethod + def setUp(self): + super().setUpClass() + self.driver = create_browser() + + def tearDown(self): + super().tearDown() + self.driver.quit() + + +class AutoLoginTest(BaseTest): + def setUp(self): + super().setUp() + self.profile = rigsmodels.Profile( + username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True) + self.profile.set_password("EventTestPassword") + self.profile.save() + loginPage = pages.LoginPage(self.driver, self.live_server_url).open() + loginPage.login("EventTest", "EventTestPassword") diff --git a/PyRIGS/tests/pages.py b/PyRIGS/tests/pages.py new file mode 100644 index 00000000..b17d837b --- /dev/null +++ b/PyRIGS/tests/pages.py @@ -0,0 +1,26 @@ +from pypom import Page +from selenium.webdriver.common.by import By + + +class BasePage(Page): + _user_locator = (By.CSS_SELECTOR, "#user>a") + + +class LoginPage(BasePage): + URL_TEMPLATE = '/user/login' + + _username_locator = (By.ID, 'id_username') + _password_locator = (By.ID, 'id_password') + _submit_locator = (By.ID, 'id_submit') + _error_locator = (By.CSS_SELECTOR, '.errorlist>li') + + def login(self, username, password): + username_element = self.find_element(*self._username_locator) + username_element.clear() + username_element.send_keys(username) + + password_element = self.find_element(*self._password_locator) + password_element.clear() + password_element.send_keys(password) + + self.find_element(*self._submit_locator).click() diff --git a/PyRIGS/tests/regions.py b/PyRIGS/tests/regions.py new file mode 100644 index 00000000..fbbb9f0e --- /dev/null +++ b/PyRIGS/tests/regions.py @@ -0,0 +1,78 @@ +from pypom import Region +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support.ui import WebDriverWait + + +def parse_bool_from_string(string): + # Used to convert from attribute strings to boolean values, written after I found this: + # >>> bool("false") + # True + if string == "true": + return True + else: + return False + + +class BootstrapSelectElement(Region): + _main_button_locator = (By.CSS_SELECTOR, 'button.dropdown-toggle') + _option_box_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu') + _option_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu.inner>li>a[role=option]') + _select_all_locator = (By.CLASS_NAME, 'bs-select-all') + _deselect_all_locator = (By.CLASS_NAME, 'bs-deselect-all') + + @property + def is_open(self): + return parse_bool_from_string(self.find_element(*self._main_button_locator).get_attribute("aria-expanded")) + + def toggle(self): + original_state = self.is_open + return self.find_element(*self._main_button_locator).click() + option_box = self.find_element(*self._option_box_locator) + if original_state: + self.wait.until(expected_conditions.invisibility_of_element_located(option_box)) + else: + self.wait.until(expected_conditions.visibility_of_element_located(option_box)) + + def open(self): + if not self.is_open: + self.toggle() + + def close(self): + if self.is_open: + self.toggle() + + def select_all(self): + self.find_element(*self._select_all_locator).click() + + def deselect_all(self): + self.find_element(*self._deselect_all_locator).click() + + @property + def options(self): + options = list(self.find_elements(*self._option_locator)) + return [self.BootstrapSelectOption(self, i) for i in options] + + def set_option(self, name, selected): + options = (x for x in self.options if x.name == name) + for option in options: + option.set_selected(selected) + + class BootstrapSelectOption(Region): + _text_locator = (By.CLASS_NAME, 'text') + + @property + def selected(self): + return parse_bool_from_string(self.root.get_attribute("aria-selected")) + + def toggle(self): + self.root.click() + + def set_selected(self, selected): + if self.selected != selected: + self.toggle() + + @property + def name(self): + return self.find_element(*self._text_locator).text diff --git a/RIGS/templates/RIGS/organisation_detail.html b/RIGS/templates/RIGS/organisation_detail.html index b12b6391..ca16aaa6 100644 --- a/RIGS/templates/RIGS/organisation_detail.html +++ b/RIGS/templates/RIGS/organisation_detail.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} + {% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% load widget_tweaks %} {% block title %}Organisation | {{ object.name }}{% endblock %} diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index aa65158a..fbe0b0b7 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -20,23 +20,12 @@ from RIGS import models from reversion import revisions as reversion from django.urls import reverse from django.core import mail, signing - - +from PyRIGS.tests.base import create_browser from django.conf import settings import sys -def create_browser(): - options = webdriver.ChromeOptions() - options.add_argument("--window-size=1920,1080") - if os.environ.get('CI', False): - options.add_argument("--headless") - options.add_argument("--no-sandbox") - driver = webdriver.Chrome(chrome_options=options) - return driver - - class UserRegistrationTest(LiveServerTestCase): def setUp(self): self.browser = create_browser() diff --git a/RIGS/test_models.py b/RIGS/test_models.py index 424e2f06..d35cbbe2 100644 --- a/RIGS/test_models.py +++ b/RIGS/test_models.py @@ -1,5 +1,3 @@ - - import pytz from reversion import revisions as reversion from django.conf import settings @@ -8,6 +6,7 @@ from django.test import TestCase from RIGS import models, versioning from datetime import date, timedelta, datetime, time from decimal import * +from PyRIGS.tests.base import create_browser class ProfileTestCase(TestCase): diff --git a/assets/templates/asset_list.html b/assets/templates/asset_list.html index 90bb2346..cbd27b28 100644 --- a/assets/templates/asset_list.html +++ b/assets/templates/asset_list.html @@ -17,16 +17,16 @@
-
+
{% render_field form.category|attr:'multiple'|add_class:'form-control selectpicker' data-none-selected-text="Categories" data-header="Categories" data-actions-box="true" %}
-
+
{% render_field form.status|attr:'multiple'|add_class:'form-control selectpicker' data-none-selected-text="Statuses" data-header="Statuses" data-actions-box="true" %}
- +
diff --git a/assets/templates/partials/asset_list_table_body.html b/assets/templates/partials/asset_list_table_body.html index c952159d..352d15db 100644 --- a/assets/templates/partials/asset_list_table_body.html +++ b/assets/templates/partials/asset_list_table_body.html @@ -1,10 +1,11 @@ {% for item in object_list %} {#
  • {{ item.asset_id }} - {{ item.description }}
  • #} - - {{ item.asset_id }} - {{ item.description }} - {{ item.category }} - {{ item.status }} + + + {{ item.asset_id }} + {{ item.description }} + {{ item.category }} + {{ item.status }}
    View diff --git a/assets/tests/__init__.py b/assets/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/assets/tests/pages.py b/assets/tests/pages.py new file mode 100644 index 00000000..ac9e8f5b --- /dev/null +++ b/assets/tests/pages.py @@ -0,0 +1,64 @@ +# Collection of page object models for use within tests. +from pypom import Page, Region +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions +from django.urls import reverse +from PyRIGS.tests import regions +from PyRIGS.tests.pages import BasePage +import pdb + + +class AssetListPage(BasePage): + URL_TEMPLATE = '/assets/asset/list' + + _asset_item_locator = (By.CLASS_NAME, 'assetRow') + _search_text_locator = (By.ID, 'id_query') + _status_select_locator = (By.CSS_SELECTOR, 'div#status-group>div.bootstrap-select') + _category_select_locator = (By.CSS_SELECTOR, 'div#category-group>div.bootstrap-select') + _go_button_locator = (By.ID, 'filter-submit') + + class AssetListRow(Region): + _asset_id_locator = (By.CLASS_NAME, "assetID") + _asset_description_locator = (By.CLASS_NAME, "assetDesc") + _asset_category_locator = (By.CLASS_NAME, "assetCategory") + _asset_status_locator = (By.CLASS_NAME, "assetStatus") + + @property + def id(self): + return self.find_element(*self._asset_id_locator).text + + @property + def description(self): + return self.find_element(*self._asset_description_locator).text + + @property + def category(self): + return self.find_element(*self._asset_category_locator).text + + @property + def status(self): + return self.find_element(*self._asset_status_locator).text + + @property + def assets(self): + return [self.AssetListRow(self, i) for i in self.find_elements(*self._asset_item_locator)] + + @property + def query(self): + return self.find_element(*self._search_text_locator).text + + def set_query(self, queryString): + element = self.find_element(*self._search_text_locator) + element.clear() + element.send_keys(queryString) + + def search(self): + self.find_element(*self._go_button_locator).click() + + @property + def status_selector(self): + return regions.BootstrapSelectElement(self, self.find_element(*self._status_select_locator)) + + @property + def category_selector(self): + return regions.BootstrapSelectElement(self, self.find_element(*self._category_select_locator)) diff --git a/assets/tests/test_assets.py b/assets/tests/test_assets.py new file mode 100644 index 00000000..1c59fc60 --- /dev/null +++ b/assets/tests/test_assets.py @@ -0,0 +1,43 @@ +from . import pages +from urllib.parse import urlparse +from RIGS import models as rigsmodels +from PyRIGS.tests.base import BaseTest, AutoLoginTest +from assets import models +import datetime + + +class AssetListTests(AutoLoginTest): + def setUp(self): + super().setUp() + sound = models.AssetCategory.objects.create(name="Sound") + lighting = models.AssetCategory.objects.create(name="Lighting") + + working = models.AssetStatus.objects.create(name="Working", should_show=True) + broken = models.AssetStatus.objects.create(name="Broken", should_show=False) + + models.Asset.objects.create(asset_id="1", description="Broken XLR", status=broken, category=sound, date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="10", description="Working Mic", status=working, category=sound, date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="2", description="A light", status=working, category=lighting, date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="C1", description="The pearl", status=broken, category=lighting, date_acquired=datetime.date(2020, 2, 1)) + self.page = pages.AssetListPage(self.driver, self.live_server_url).open() + + def test_default_statuses_applied(self): + # Only the working stuff should be shown initially + assetDescriptions = list(map(lambda x: x.description, self.page.assets)) + self.assertEqual(2, len(assetDescriptions)) + self.assertIn("A light", assetDescriptions) + self.assertIn("Working Mic", assetDescriptions) + + def test_asset_order(self): + # Only the working stuff should be shown initially + self.page.status_selector.open() + self.page.status_selector.set_option("Broken", True) + self.page.status_selector.close() + + self.page.search() + + assetIDs = list(map(lambda x: x.id, self.page.assets)) + self.assertEqual("1", assetIDs[0]) + self.assertEqual("2", assetIDs[1]) + self.assertEqual("10", assetIDs[2]) + self.assertEqual("C1", assetIDs[3]) diff --git a/assets/views.py b/assets/views.py index 96e8940d..4b1e6b98 100644 --- a/assets/views.py +++ b/assets/views.py @@ -213,7 +213,8 @@ class SupplierSearch(SupplierList): for supplier in context["object_list"]: result.append({"id": supplier.pk, "name": supplier.name}) - + import pdb + pdb.set_trace() return JsonResponse(result, safe=False) diff --git a/requirements.txt b/requirements.txt index 96e43b97..2712ea20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,3 +38,4 @@ z3c.rml==3.5.0 zope.event==4.3.0 zope.interface==4.5.0 zope.schema==4.5.0 +pypom==2.2.0 \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 53238def..a902a7ab 100644 --- a/templates/base.html +++ b/templates/base.html @@ -52,7 +52,7 @@ {% endblock %}