mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-17 05:22:16 +00:00
* Add basic markdown support site wide
* Improved MD support.
Add some styling for images in MD
Add support for the bastardisation of the MD html for RML.
* Add processing for <ul> in RML
* Add OL processing to RML
* Fix a bug with squares appearing around the last page number
* Remove rml formatting in event_detail
* Improve handling of code blocks in RML
* Add MD to rigboard
Reduce MD title sizes as they were offensively large
* Add parsing of markdown when editing event items
* Improved list handling in RML
* Add tests for markdown support.
Focuses mainly on RML as that's where it will break
* Add indications of where MD support is enabled as per comment by @samozzy in #178.
Isn't quite a full description, but for the most part this should be enough for the people who know how to use it see where they can use it.
* Add failing test for markdown processing none
* Fix for failing test in e0d56e
* Add failing test for using single line breaks as per comment on #214
* Enable line break extension for single breaks in paragraphs by new lines.
Pass tests in ef3de607c3
* Enable GH flavour linebreaks in JS rendered markdown
* Made RML bullets pretty :)
* Added WYSIWYG editor. Works for notes & description, fails miserably for items :(
* Fixed for event items. Will probably fail tests because selenium can't type in simpleMDE :(
* FIX: Re-enable markdown on paperwork
Strikethrough is broken in all sorts of places for whatever reason
* FEAT: Markdown support on asset comments
* FIX: Prevent js injection through markdown fields
* Initial fixes
* Basic dark theme for simplemde
* Swap to locally delivered SimpleMDE
* Region for selenium testing of SimpleMDE
Bleh, Javascript all around
* Tests passing!
Fixed not using region for item modal, and overflow error on paperwork with really long description. Looks junk but I'm not really bothered
* Pep8 fixes
* Fallback for null HCapatcha sitekey
I.e. when we're on a branch
* Fix item description print being broken
* Actually fix sitekey problem
* Fixes for using markdown in asset comments
* Properly initialise markdown on asset comments
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: FreneticScribbler <aj@aronajones.com>
252 lines
9.0 KiB
Python
252 lines
9.0 KiB
Python
# Collection of page object models for use within tests.
|
|
from django.urls import reverse
|
|
from pypom import Region
|
|
from selenium.common.exceptions import NoSuchElementException
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.support import expected_conditions
|
|
|
|
from PyRIGS.tests import regions
|
|
from PyRIGS.tests.pages import BasePage, FormPage, animation_is_finished
|
|
|
|
|
|
class AssetList(BasePage):
|
|
URL_TEMPLATE = '/assets/asset/list'
|
|
|
|
_asset_item_locator = (By.CLASS_NAME, 'assetRow')
|
|
_search_text_locator = (By.ID, 'id_q')
|
|
_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, 'id_search')
|
|
_filter_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()
|
|
|
|
def filter(self):
|
|
self.find_element(*self._filter_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))
|
|
|
|
|
|
class AssetForm(FormPage):
|
|
_purchased_from_select_locator = (By.XPATH, '//div[@id="purchased-from-group"]/div/div/div')
|
|
_parent_select_locator = (By.CSS_SELECTOR, 'div#parent-group>div.bootstrap-select')
|
|
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')),
|
|
'comments': (regions.SimpleMDETextArea, (By.ID, 'id_comments')),
|
|
'purchase_price': (regions.TextBox, (By.ID, 'id_purchase_price')),
|
|
'salvage_value': (regions.TextBox, (By.ID, 'id_salvage_value')),
|
|
'date_acquired': (regions.DatePicker, (By.ID, 'id_date_acquired')),
|
|
'date_sold': (regions.DatePicker, (By.ID, 'id_date_sold')),
|
|
'category': (regions.SingleSelectPicker, (By.ID, 'id_category')),
|
|
'status': (regions.SingleSelectPicker, (By.ID, 'id_status')),
|
|
|
|
'cable_type': (regions.SingleSelectPicker, (By.ID, 'id_cable_type')),
|
|
'length': (regions.TextBox, (By.ID, 'id_length')),
|
|
'csa': (regions.TextBox, (By.ID, 'id_csa')),
|
|
}
|
|
|
|
@property
|
|
def purchased_from_selector(self):
|
|
return regions.BootstrapSelectElement(self, self.find_element(*self._purchased_from_select_locator))
|
|
|
|
@property
|
|
def parent_selector(self):
|
|
return regions.BootstrapSelectElement(self, self.find_element(*self._parent_select_locator))
|
|
|
|
|
|
class AssetEdit(AssetForm):
|
|
URL_TEMPLATE = '/assets/asset/id/{asset_id}/edit/'
|
|
|
|
@property
|
|
def success(self):
|
|
return '/edit' not in self.driver.current_url
|
|
|
|
|
|
class AssetCreate(AssetForm):
|
|
URL_TEMPLATE = '/assets/asset/create/'
|
|
|
|
@property
|
|
def success(self):
|
|
return '/create' not in self.driver.current_url
|
|
|
|
|
|
class AssetDuplicate(AssetForm):
|
|
URL_TEMPLATE = '/assets/asset/id/{asset_id}/duplicate'
|
|
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Duplicate')]")
|
|
|
|
@property
|
|
def success(self):
|
|
return '/duplicate' not in self.driver.current_url
|
|
|
|
|
|
class SupplierList(BasePage):
|
|
URL_TEMPLATE = reverse('supplier_list')
|
|
|
|
_supplier_item_locator = (By.ID, 'row_item')
|
|
_search_text_locator = (By.ID, 'id_search_text')
|
|
_go_button_locator = (By.ID, 'id_search')
|
|
|
|
class SupplierListRow(Region):
|
|
_name_locator = (By.ID, "cell_name")
|
|
|
|
@property
|
|
def name(self):
|
|
return self.find_element(*self._name_locator).text
|
|
|
|
@property
|
|
def suppliers(self):
|
|
return [self.SupplierListRow(self, i) for i in self.find_elements(*self._supplier_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()
|
|
|
|
|
|
class SupplierForm(FormPage):
|
|
form_items = {
|
|
'name': (regions.TextBox, (By.ID, 'id_name')),
|
|
}
|
|
|
|
|
|
class SupplierCreate(SupplierForm):
|
|
URL_TEMPLATE = reverse('supplier_create')
|
|
|
|
@property
|
|
def success(self):
|
|
return '/create' not in self.driver.current_url
|
|
|
|
|
|
class SupplierEdit(SupplierForm):
|
|
# TODO This should be using reverse
|
|
URL_TEMPLATE = '/assets/supplier/{supplier_id}/edit'
|
|
|
|
@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_q')
|
|
_go_button_locator = (By.ID, 'searchButton')
|
|
_modal_locator = (By.ID, 'modal')
|
|
_errors_selector = (By.CLASS_NAME, "alert-danger")
|
|
|
|
@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, query):
|
|
element = self.find_element(*self._search_text_locator)
|
|
element.clear()
|
|
element.send_keys(query)
|
|
|
|
def search(self):
|
|
self.find_element(*self._go_button_locator).click()
|
|
self.wait.until(animation_is_finished())
|
|
|
|
@property
|
|
def error(self):
|
|
try:
|
|
return self.find_element(*self._errors_selector)
|
|
except NoSuchElementException:
|
|
return None
|
|
|
|
class AssetAuditModal(regions.Modal):
|
|
_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")
|
|
_close_selector = (By.XPATH, "//button[@data-dismiss='modal']")
|
|
|
|
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 errors(self):
|
|
try:
|
|
error_page = regions.ErrorPage(self, self.find_element(*self._errors_selector))
|
|
return error_page.errors
|
|
except NoSuchElementException:
|
|
return None
|
|
|
|
def close(self):
|
|
self.page.find_element(*self._close_selector).click()
|
|
self.wait.until(expected_conditions.invisibility_of_element_located((By.ID, 'modal')))
|
|
|
|
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\")});")
|