mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-10 00:39:40 +00:00
Started POM and assets test
This commit is contained in:
37
PyRIGS/tests/base.py
Normal file
37
PyRIGS/tests/base.py
Normal file
@@ -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")
|
||||||
26
PyRIGS/tests/pages.py
Normal file
26
PyRIGS/tests/pages.py
Normal file
@@ -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()
|
||||||
78
PyRIGS/tests/regions.py
Normal file
78
PyRIGS/tests/regions.py
Normal file
@@ -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
|
||||||
@@ -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 %}
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% block title %}Organisation | {{ object.name }}{% endblock %}
|
{% block title %}Organisation | {{ object.name }}{% endblock %}
|
||||||
|
|||||||
@@ -20,23 +20,12 @@ from RIGS import models
|
|||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.core import mail, signing
|
from django.core import mail, signing
|
||||||
|
from PyRIGS.tests.base import create_browser
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
import sys
|
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):
|
class UserRegistrationTest(LiveServerTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.browser = create_browser()
|
self.browser = create_browser()
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -8,6 +6,7 @@ from django.test import TestCase
|
|||||||
from RIGS import models, versioning
|
from RIGS import models, versioning
|
||||||
from datetime import date, timedelta, datetime, time
|
from datetime import date, timedelta, datetime, time
|
||||||
from decimal import *
|
from decimal import *
|
||||||
|
from PyRIGS.tests.base import create_browser
|
||||||
|
|
||||||
|
|
||||||
class ProfileTestCase(TestCase):
|
class ProfileTestCase(TestCase):
|
||||||
|
|||||||
@@ -17,16 +17,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div style="margin-top: 1em;" class="pull-right">
|
<div style="margin-top: 1em;" class="pull-right">
|
||||||
<div class="form-group">
|
<div id="category-group" class="form-group">
|
||||||
<label for="category" class="sr-only">Category</label>
|
<label for="category" class="sr-only">Category</label>
|
||||||
{% 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.category|attr:'multiple'|add_class:'form-control selectpicker' data-none-selected-text="Categories" data-header="Categories" data-actions-box="true" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div id="status-group" class="form-group">
|
||||||
<label for="status" class="sr-only">Status</label>
|
<label for="status" class="sr-only">Status</label>
|
||||||
{% render_field form.status|attr:'multiple'|add_class:'form-control selectpicker' data-none-selected-text="Statuses" data-header="Statuses" 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" %}
|
||||||
</div>
|
</div>
|
||||||
<!---TODO: Auto filter whenever an option is selected, instead of using a button -->
|
<!---TODO: Auto filter whenever an option is selected, instead of using a button -->
|
||||||
<button type="submit" class="btn btn-default">Filter</button>
|
<button id="filter-submit" type="submit" class="btn btn-default">Filter</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{% for item in object_list %}
|
{% for item in object_list %}
|
||||||
{# <li><a href="{% url 'asset_detail' item.pk %}">{{ item.asset_id }} - {{ item.description }}</a></li>#}
|
{# <li><a href="{% url 'asset_detail' item.pk %}">{{ item.asset_id }} - {{ item.description }}</a></li>#}
|
||||||
<!---TODO: When the ability to filter the list is added, remove the colours from the filter - specifically, stop greying out sold/binned stuff if it is being searched for--> <tr class={{ item.status.display_class|default:"" }}>
|
<!---TODO: When the ability to filter the list is added, remove the colours from the filter - specifically, stop greying out sold/binned stuff if it is being searched for-->
|
||||||
<td style="vertical-align: middle;"><a href="{% url 'asset_detail' item.asset_id %}">{{ item.asset_id }}</a></td>
|
<tr class="{{ item.status.display_class|default:'' }} assetRow">
|
||||||
<td style="vertical-align: middle; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; max-width: 25vw">{{ item.description }}</td>
|
<td style="vertical-align: middle;"><a class="assetID" href="{% url 'asset_detail' item.asset_id %}">{{ item.asset_id }}</a></td>
|
||||||
<td style="vertical-align: middle;">{{ item.category }}</td>
|
<td class="assetDesc" style="vertical-align: middle; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; max-width: 25vw">{{ item.description }}</td>
|
||||||
<td style="vertical-align: middle;">{{ item.status }}</td>
|
<td class="assetCategory" style="vertical-align: middle;">{{ item.category }}</td>
|
||||||
|
<td class="assetStatus" style="vertical-align: middle;">{{ item.status }}</td>
|
||||||
<td class="hidden-xs">
|
<td class="hidden-xs">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<a type="button" class="btn btn-default btn-sm" href="{% url 'asset_detail' item.asset_id %}"><i class="glyphicon glyphicon-eye-open"></i> View</a>
|
<a type="button" class="btn btn-default btn-sm" href="{% url 'asset_detail' item.asset_id %}"><i class="glyphicon glyphicon-eye-open"></i> View</a>
|
||||||
|
|||||||
0
assets/tests/__init__.py
Normal file
0
assets/tests/__init__.py
Normal file
64
assets/tests/pages.py
Normal file
64
assets/tests/pages.py
Normal file
@@ -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))
|
||||||
43
assets/tests/test_assets.py
Normal file
43
assets/tests/test_assets.py
Normal file
@@ -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])
|
||||||
@@ -213,7 +213,8 @@ class SupplierSearch(SupplierList):
|
|||||||
|
|
||||||
for supplier in context["object_list"]:
|
for supplier in context["object_list"]:
|
||||||
result.append({"id": supplier.pk, "name": supplier.name})
|
result.append({"id": supplier.pk, "name": supplier.name})
|
||||||
|
import pdb
|
||||||
|
pdb.set_trace()
|
||||||
return JsonResponse(result, safe=False)
|
return JsonResponse(result, safe=False)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,3 +38,4 @@ z3c.rml==3.5.0
|
|||||||
zope.event==4.3.0
|
zope.event==4.3.0
|
||||||
zope.interface==4.5.0
|
zope.interface==4.5.0
|
||||||
zope.schema==4.5.0
|
zope.schema==4.5.0
|
||||||
|
pypom==2.2.0
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li class="dropdown">
|
<li class="dropdown" id="user">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||||
<span class="glyphicon glyphicon-user"></span>
|
<span class="glyphicon glyphicon-user"></span>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<a href="{% url 'registration_register' %}" class="btn">Register</a>
|
<a href="{% url 'registration_register' %}" class="btn">Register</a>
|
||||||
<a href="{% url 'password_reset' %}" class="btn">Forgotten Password</a>
|
<a href="{% url 'password_reset' %}" class="btn">Forgotten Password</a>
|
||||||
<input type="submit" value="Login" class="btn btn-primary"/>
|
<input type="submit" id="id_submit" value="Login" class="btn btn-primary"/>
|
||||||
<input type="hidden" name="next" value="{{ next }}"/>
|
<input type="hidden" name="next" value="{{ next }}"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user