Started POM and assets test

This commit is contained in:
Matthew Smith
2020-01-23 18:29:18 +00:00
parent 630011aff7
commit a25c41150e
15 changed files with 265 additions and 26 deletions

37
PyRIGS/tests/base.py Normal file
View 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
View 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
View 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

View File

@@ -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 %}

View File

@@ -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()

View File

@@ -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):

View File

@@ -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>

View File

@@ -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
View File

64
assets/tests/pages.py Normal file
View 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))

View 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])

View File

@@ -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)

View File

@@ -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

View File

@@ -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>

View File

@@ -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>