diff --git a/assets/templates/asset_audit.html b/assets/templates/asset_audit.html
index 748b0d4f..caaee4d7 100644
--- a/assets/templates/asset_audit.html
+++ b/assets/templates/asset_audit.html
@@ -142,7 +142,7 @@
{% if not request.is_ajax %}
-
+
{% endif %}
@@ -150,6 +150,6 @@
{% block footer %}
-
+
{% endblock %}
diff --git a/assets/tests/pages.py b/assets/tests/pages.py
index e31859e1..f0665133 100644
--- a/assets/tests/pages.py
+++ b/assets/tests/pages.py
@@ -6,7 +6,7 @@ from selenium.webdriver import Chrome
from django.urls import reverse
from PyRIGS.tests import regions
from PyRIGS.tests.pages import BasePage, FormPage
-import pdb
+from selenium.common.exceptions import NoSuchElementException
class AssetList(BasePage):
@@ -186,3 +186,109 @@ class SupplierEdit(SupplierForm):
@property
def success(self):
return '/edit' not in self.driver.current_url
+
+
+class AssetAuditList(AssetList):
+ URL_TEMPLATE = reverse('asset_audit_list')
+
+ _search_text_locator = (By.ID, 'id_query')
+ _go_button_locator = (By.ID, 'searchButton')
+ _modal_locator = (By.ID, 'modal')
+
+ @property
+ def modal(self):
+ return self.AssetAuditModal(self, self.find_element(*self._modal_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()
+
+ class AssetAuditModal(Region):
+ _errors_selector = (By.CLASS_NAME, "alert-danger")
+ # Don't use the usual success selector - that tries and fails to hit the '10m long cable' helper button...
+ _submit_locator = (By.ID, "id_mark_audited")
+ form_items = {
+ 'asset_id': (regions.TextBox, (By.ID, 'id_asset_id')),
+ 'description': (regions.TextBox, (By.ID, 'id_description')),
+ 'is_cable': (regions.CheckBox, (By.ID, 'id_is_cable')),
+ 'serial_number': (regions.TextBox, (By.ID, 'id_serial_number')),
+ 'salvage_value': (regions.TextBox, (By.ID, 'id_salvage_value')),
+ 'date_acquired': (regions.DatePicker, (By.ID, 'id_date_acquired')),
+ 'category': (regions.SingleSelectPicker, (By.ID, 'id_category')),
+ 'status': (regions.SingleSelectPicker, (By.ID, 'id_status')),
+
+ 'plug': (regions.SingleSelectPicker, (By.ID, 'id_plug')),
+ 'socket': (regions.SingleSelectPicker, (By.ID, 'id_socket')),
+ 'length': (regions.TextBox, (By.ID, 'id_length')),
+ 'csa': (regions.TextBox, (By.ID, 'id_csa')),
+ 'circuits': (regions.TextBox, (By.ID, 'id_circuits')),
+ 'cores': (regions.TextBox, (By.ID, 'id_cores'))
+ }
+
+ @property
+ def is_displayed(self):
+ return self.root.is_displayed()
+
+ @property
+ def errors(self):
+ try:
+ error_page = self.ErrorPage(self, self.find_element(*self._errors_selector))
+ return error_page.errors
+ except NoSuchElementException:
+ return None
+
+ def submit(self):
+ previous_errors = self.errors
+ self.root.find_element(*self._submit_locator).click()
+ # self.wait.until(lambda x: not self.is_displayed)
+
+ class ErrorPage(Region):
+ _error_item_selector = (By.CSS_SELECTOR, "dl>span")
+
+ class ErrorItem(Region):
+ _field_selector = (By.CSS_SELECTOR, "dt")
+ _error_selector = (By.CSS_SELECTOR, "dd>ul>li")
+
+ @property
+ def field_name(self):
+ return self.find_element(*self._field_selector).text
+
+ @property
+ def errors(self):
+ return [x.text for x in self.find_elements(*self._error_selector)]
+
+ @property
+ def errors(self):
+ error_items = [self.ErrorItem(self, x) for x in self.find_elements(*self._error_item_selector)]
+ errors = {}
+ for error in error_items:
+ errors[error.field_name] = error.errors
+ return errors
+
+ def remove_all_required(self):
+ self.driver.execute_script("Array.from(document.getElementsByTagName(\"input\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
+ self.driver.execute_script("Array.from(document.getElementsByTagName(\"select\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
+
+ def __getattr__(self, name):
+ if name in self.form_items:
+ element = self.form_items[name]
+ form_element = element[0](self, self.find_element(*element[1]))
+ return form_element.value
+ else:
+ return super().__getattribute__(name)
+
+ def __setattr__(self, name, value):
+ if name in self.form_items:
+ element = self.form_items[name]
+ form_element = element[0](self, self.find_element(*element[1]))
+ form_element.set_value(value)
+ else:
+ self.__dict__[name] = value
diff --git a/assets/tests/test_assets.py b/assets/tests/test_assets.py
index 9a96fcbe..778cba2e 100644
--- a/assets/tests/test_assets.py
+++ b/assets/tests/test_assets.py
@@ -10,6 +10,8 @@ from PyRIGS.tests.base import BaseTest, AutoLoginTest
from assets import models, urls
from reversion import revisions as reversion
from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.support.ui import WebDriverWait
+from RIGS.test_functional import animation_is_finished
import datetime
@@ -256,6 +258,54 @@ class TestSupplierCreateAndEdit(AutoLoginTest):
self.assertTrue(self.page.success)
+class TestAssetAudit(AutoLoginTest):
+ def setUp(self):
+ super().setUp()
+ self.category = models.AssetCategory.objects.create(name="Haulage")
+ self.status = models.AssetStatus.objects.create(name="Probably Fine", should_show=True)
+ self.supplier = models.Supplier.objects.create(name="The Bazaar")
+ self.connector = models.Connector.objects.create(description="Trailer Socket", current_rating=1, voltage_rating=40, num_pins=13)
+ models.Asset.objects.create(asset_id="1", description="Trailer Cable", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1))
+ models.Asset.objects.create(asset_id="11", description="Trailerboard", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1))
+ models.Asset.objects.create(asset_id="111", description="Erms", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1))
+ models.Asset.objects.create(asset_id="1111", description="A hammer", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1))
+ self.page = pages.AssetAuditList(self.driver, self.live_server_url).open()
+
+ def test_audit_process(self):
+ asset_id = "1111"
+ self.page.set_query(asset_id)
+ self.page.search()
+ wait = WebDriverWait(self.driver, 3)
+ wait.until(animation_is_finished())
+
+ mdl = self.page.modal
+ self.assertTrue(mdl.is_displayed)
+ # Do it wrong on purpose to check error display
+ mdl.remove_all_required()
+ mdl.description = ""
+ mdl.submit()
+ wait.until(animation_is_finished())
+ self.assertTrue(mdl.is_displayed)
+ self.assertIn("This field is required.", mdl.errors["Description"])
+ # Now do it properly
+ new_desc = "A BIG hammer"
+ mdl.description = new_desc
+ mdl.submit()
+ wait.until(animation_is_finished())
+ self.assertFalse(mdl.is_displayed)
+
+ # Check data is correct
+ audited = models.Asset.objects.get(asset_id="1111")
+ self.assertEqual(audited.description, new_desc)
+ # Make sure audit 'log' was filled out
+ self.assertEqual(self.profile.initials, audited.last_audited_by.initials)
+ self.assertEqual(datetime.datetime.now().date(), audited.last_audited_at.date())
+ self.assertEqual(datetime.datetime.now().hour, audited.last_audited_at.hour)
+ self.assertEqual(datetime.datetime.now().minute, audited.last_audited_at.minute)
+ # Check we've removed it from the 'needing audit' list
+ self.assertNotIn(asset_id, self.page.assets)
+
+
class TestSupplierValidation(TestCase):
@classmethod
def setUpTestData(cls):