Added Asset Create and Edit forms

This commit is contained in:
Matthew Smith
2020-02-05 15:35:07 +00:00
parent 39b22690f1
commit 5265ef835e
7 changed files with 189 additions and 11 deletions

View File

@@ -16,7 +16,6 @@ def create_browser():
class BaseTest(LiveServerTestCase): class BaseTest(LiveServerTestCase):
@classmethod
def setUp(self): def setUp(self):
super().setUpClass() super().setUpClass()
self.driver = create_browser() self.driver = create_browser()

View File

@@ -1,9 +1,69 @@
from pypom import Page from pypom import Page, Region
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver import Chrome
from selenium.common.exceptions import NoSuchElementException
class BasePage(Page): class BasePage(Page):
_user_locator = (By.CSS_SELECTOR, "#user>a") form_items = {}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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
class FormPage(BasePage):
_errors_selector = (By.CLASS_NAME, "alert-danger")
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\")});")
@property
def errors(self):
try:
error_page = self.ErrorPage(self, self.find_element(*self._errors_selector))
return error_page.errors
except NoSuchElementException:
return None
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
class LoginPage(BasePage): class LoginPage(BasePage):

View File

@@ -3,6 +3,8 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions from selenium.webdriver.support import expected_conditions
from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.select import Select
import datetime
def parse_bool_from_string(string): def parse_bool_from_string(string):
@@ -19,8 +21,10 @@ class BootstrapSelectElement(Region):
_main_button_locator = (By.CSS_SELECTOR, 'button.dropdown-toggle') _main_button_locator = (By.CSS_SELECTOR, 'button.dropdown-toggle')
_option_box_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu') _option_box_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu')
_option_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu.inner>li>a[role=option]') _option_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu.inner>li>a[role=option]')
_select_all_locator = (By.CLASS_NAME, 'bs-select-all') _select_all_locator = (By.CLASS_NAME, '.bs-select-all')
_deselect_all_locator = (By.CLASS_NAME, 'bs-deselect-all') _deselect_all_locator = (By.CLASS_NAME, '.bs-deselect-all')
_search_locator = (By.CSS_SELECTOR, '.bs-searchbox>input')
_status_locator = (By.CLASS_NAME, 'status')
@property @property
def is_open(self): def is_open(self):
@@ -49,15 +53,22 @@ class BootstrapSelectElement(Region):
def deselect_all(self): def deselect_all(self):
self.find_element(*self._deselect_all_locator).click() self.find_element(*self._deselect_all_locator).click()
def search(self, query):
search_box = self.find_element(*self._search_locator)
search_box.clear()
search_box.send_keys(query)
status_text = self.find_element(*self._status_locator)
self.wait.until(expected_conditions.invisibility_of_element_located(self._status_locator))
@property @property
def options(self): def options(self):
options = list(self.find_elements(*self._option_locator)) options = list(self.find_elements(*self._option_locator))
return [self.BootstrapSelectOption(self, i) for i in options] return [self.BootstrapSelectOption(self, i) for i in options]
def set_option(self, name, selected): def set_option(self, name, selected):
options = (x for x in self.options if x.name == name) options = list((x for x in self.options if x.name == name))
for option in options: assert len(options) == 1
option.set_selected(selected) options[0].set_selected(selected)
class BootstrapSelectOption(Region): class BootstrapSelectOption(Region):
_text_locator = (By.CLASS_NAME, 'text') _text_locator = (By.CLASS_NAME, 'text')
@@ -76,3 +87,47 @@ class BootstrapSelectElement(Region):
@property @property
def name(self): def name(self):
return self.find_element(*self._text_locator).text return self.find_element(*self._text_locator).text
class TextBox(Region):
@property
def value(self):
return self.root.get_attribute("value")
def set_value(self, value):
self.root.clear()
self.root.send_keys(value)
class CheckBox(Region):
def toggle(self):
self.root.click()
@property
def value(self):
return parse_bool_from_string(self.root.get_attribute("checked"))
def set_value(self, value):
if value != self.value:
self.toggle()
class DatePicker(Region):
@property
def value(self):
return datetime.datetime.strptime(self.root.get_attribute("value"), "%Y-%m-%d")
def set_value(self, value):
self.root.clear()
self.root.send_keys(value.strftime("%d%m%Y"))
class SingleSelectPicker(Region):
@property
def value(self):
picker = Select(self.root)
return picker.first_selected_option.text
def set_value(self, value):
picker = Select(self.root)
picker.select_by_visible_text(value)

View File

@@ -6,7 +6,7 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% if create or edit or duplicate %} {% if create or edit or duplicate %}
<div class="form-group"> <div class="form-group" id="parent-group">
<label for="selectpicker">Set Parent</label> <label for="selectpicker">Set Parent</label>
{% include 'partials/asset_picker.html' %} {% include 'partials/asset_picker.html' %}
</div> </div>

View File

@@ -23,7 +23,7 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% if create or edit or duplicate %} {% if create or edit or duplicate %}
<div class="form-group"> <div class="form-group" id="purchased-from-group">
<label for="{{ form.purchased_from.id_for_label }}">Supplier</label> <label for="{{ form.purchased_from.id_for_label }}">Supplier</label>
<select id="{{ form.purchased_from.id_for_label }}" name="{{ form.purchased_from.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='supplier' %}"> <select id="{{ form.purchased_from.id_for_label }}" name="{{ form.purchased_from.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='supplier' %}">
{% if object.purchased_from %} {% if object.purchased_from %}

View File

@@ -2,9 +2,10 @@
from pypom import Page, Region from pypom import Page, Region
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions from selenium.webdriver.support import expected_conditions
from selenium.webdriver import Chrome
from django.urls import reverse from django.urls import reverse
from PyRIGS.tests import regions from PyRIGS.tests import regions
from PyRIGS.tests.pages import BasePage from PyRIGS.tests.pages import BasePage, FormPage
import pdb import pdb
@@ -62,3 +63,64 @@ class AssetListPage(BasePage):
@property @property
def category_selector(self): def category_selector(self):
return regions.BootstrapSelectElement(self, self.find_element(*self._category_select_locator)) return regions.BootstrapSelectElement(self, self.find_element(*self._category_select_locator))
class AssetForm(FormPage):
_purchased_from_select_locator = (By.CSS_SELECTOR, 'div#purchased-from-group>div.bootstrap-select')
_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.TextBox, (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_category')),
'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 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/'
_submit_locator = (By.CLASS_NAME, 'btn-success')
def submit(self):
previous_errors = self.errors
self.find_element(*self._submit_locator).click()
self.wait.until(lambda x: self.errors != previous_errors or self.success)
@property
def success(self):
return '/edit' not in self.driver.current_url
class AssetCreate(AssetForm):
URL_TEMPLATE = '/assets/asset/create/'
_submit_locator = (By.CLASS_NAME, 'btn-success')
def submit(self):
previous_errors = self.errors
self.find_element(*self._submit_locator).click()
self.wait.until(lambda x: self.errors != previous_errors or self.success)
@property
def success(self):
return '/create' not in self.driver.current_url

View File

@@ -5,8 +5,10 @@
<dl class="dl-horizontal"> <dl class="dl-horizontal">
{% with form|nice_errors as qq %} {% with form|nice_errors as qq %}
{% for error_name,desc in qq.items %} {% for error_name,desc in qq.items %}
<span>
<dt>{{error_name}}</dt> <dt>{{error_name}}</dt>
<dd>{{desc}}</dd> <dd>{{desc}}</dd>
</span>
{% endfor %} {% endfor %}
{% endwith %} {% endwith %}
</dl> </dl>