Welcome to Behave Manners’s documentation!¶
Topics:
Manners Overview¶
behave_manners is designed to be a weapon against complexity of modern web sites (rather, applications). Offers utility methods and a powerful abstraction layer on top of Web Elements, the standard objects of WebDriver API.
‘manners’ offers tools in these areas:
- setting up the ‘site’ and calling the client browser
- navigating through the site, by URLs and matching ‘known pages’
- decoding the remote DOM into an abstract structure of components
- consistent error handling, logs and screenshots as a separate layer
Also, some early work is done in enriching the Gherkin language with more structure, re-usable scenarios and resources.
Introduction to Page Elements¶
Page elements (or pagelems) is a custom html-like language of defining patterns for matching remote DOM trees. This language is designed to resemble HTML as much as possible, but is NOT the kind of html that would render into the DOM of a page.
Rather, it is the inverse: it is a declarative language of parsing that DOM.
Rules/principles¶
Understanding the design of page elements comes after considering the following:
Principles
- Markup shall be as simple as possible
- Markup must resemble HTML, share concepts/features of HTML5
- developer should write enough markup to uniquely identify components
- Markup shall provide a way to mark components discovered, and their attributes
- Markup is by design nested, a lot
- Markup shall provide ways to share blocks and refer back and forth to others
Rules
- plain markup should match the same markup on the remote
eg.
<div class="foo"><span>Text</span></div>
should match a DIV containing a span with text=”Text” etc. - attributes and elements not mentioned DO match
In the example above, all other attributes of the
<div>
or some other element within that should be allowed. - children elements match children in the DOM, direct descendants unless
explicitly decorated (with
pe-deep
) - some special elements+attributes, all decorated with the pe- prefix may modify this straightforward HTML matching
- the special
this
attribute defines a component. All other elements are just matched, harvested for attributes, but not exposed. - attributes defined multiple times may be OR-ed together
Special elements¶
Define constructs that modify the way DOM is structured.
<pe-repeat>
Causes its contents to be matched multiple times, repeated naturally.
Note
this is implied if
this
has a flexible definition, see below.
<pe-choice>
Attempts to match the first of the contained components. Any of them may match.
<pe-any>
Matches any html element. Otherwise its attributes and children match like any plain element.
<pe-data>
Assigns arbitrary data as an attribute to current component. Like a constant to some programming language. May serve as customization data for re-usable controllers on that component.
<pe-regex>
Matches element text by regular expression; then assigns attributes from named members of that expression
<pe-deep>
Matches (its children) at any level deep from the parent element
<pe-root>
Resets nesting to the DOM document root, matches children down from the root
<pe-slotcontent>
Points back to the original content of the slot. See Templates and Slots
<pe-group>
Matches all of the contained elements, in order. pe-group itself needs not match any element. Useful for<pe-choice>
and repetitions
Special attributes¶
pe-deep
Matches this element any level down from the parent, not just direct ancestors
pe-optional
Makes this element (and all contents) optional. No error if cannot match
pe-controller
pe-ctrl
Picks an alternate controller, defines a scope at that level. See Scopes and Controllers
slot
Defines slot name. See Templates and Slots
Special attribute: this¶
this
deserves a section of its own.
It defines a component in the matched tree.
In its simplest form: this="egg"
, it would define a component named “egg”
In the attribute form: this="[title]"
it will scan the contents, then resolve
the title attribute, use the title’s value as a name.
In the parametric form: this="col_%d"
it may produce more than one components
using the integer count of matches.
Example:
<div class="chapter" this="first-chapter">
<section this="[title]">
<h3>[title]</h3>
<p this="par_%d">
[content]
</p>
</section>
</div>
The above would produce a Component tree like:
first-chapter/
Some Title/
par_0/
content="..."
par_1/
content="..."
Other Title/
par_0/
content="..."
Which exposes, say, that second content in python as:
root['first-chapter']['Some Title']['par_1'].content
Templates and Slots¶
Templates are described in HTML5, are a way for the browser to repeat rendering some block of HTML in multiple places of the DOM. Likewise, in pagelements, they allow blocks of pagelem markup to be re-used across the page or any components.
Example:
<template id="complete-text-field">
<div class="field-container" this="[name]">
<div>
<label>[field]</label>
<input pe-deep type="text" name="[name]" this="input">
</div>
</div>
</template>
then:
<form this="the-form">
<div class="form-container">
<!-- matches all text fields, indexes by their `name` -->
<use-template id="complete-text-field"/>
<!-- matches that `name="size"` number field, explicitly -->
<div class="field-container" this="the-size">
<input pe-deep type="number" name="size" this="input"/>
</div>
</div>
</form>
Templates can be defined in the <head> of the pagelem html, in the <body> or in separate gallery files, from where they can be re-used.
Templates can have custom content, per call, that will be substitued in the pagelem markup:
<template id="custom-text-field">
<div class="field-container" this="[name]">
<div>
<slot name="label">
<label>[field]</label>
</slot>
<input pe-deep type="text" name="[name]" this="input">
</div>
</div>
</template>
<form>
<use-template id="custom-text-field">
<!-- matches that label only -->
<label slot="label">Just this one</label>
</use-template>
<use-template id="custom-text-field">
<span slot="label">Odd field</span> <!-- this uses different element -->
</use-template>
</form>
Scopes and Controllers¶
Scopes and Controllers are a way to inject extra behaviour onto components, from code written in Python. They are able to abstract non-trivial interactions as if they were instance methods to the component objects.
Scopes have their own tree (hierarchy) are deliberately not 1:1 with components. Reason is to keep code minimal, not to mandate extra code per each component. In practice, few of the components, only, will ever need their own controller.
Note
scopes and controllers are used as terms interchangeably, they are the same thing. A controller is the class that defines custom behaviour, a scope is the instantiated object of that class, that could also have state.
Service Metaclass¶
Controller classes are using the Service Meta class, derive from DOMScope
.
This means that they can be referenced by _name
, and can be overriden by defining
the same name in some custom python module.
Inheritance¶
Apart from python (or rather service-meta) inheritance, scopes also hold a reference of their parent in the Component Tree. By default, they inherit the attributes of that parent scope. This allows nested components to call parent scope’s methods, or even to modify that parent’s state.
In the example below, suppose that there is a minimal HTML document (into components) with one component calling a custom controller (the ‘fld-ctrl’ one).
In python, suppose the following code defines the controller classes:
class MyScope(DOMScope):
_name = 'page'
def hello(self):
return "Hello!"
class FldCtrl(DOMScope):
_name = 'fld-ctrl'
def check_field(self, field):
return field.value == 'ham'
Scope inheritance would look like this diagram:
There is always a root scope (usually the .root
class) [1] and a page one[2].
Also, all scopes inherit DOMScope
which is
denoted by [*]
.
But our MyScope
class redefines the page, so it would have methods from both
‘page’ classes. So, anywhere in the document, components could say:
content._scope.hello()
and use that method. Including the ‘field’ component.
Then, the field component has its own controller [3], hence scope. This means that the field component, including any sub-components there, can say:
field_component._scope.check_field(sub_component)
Controllers of components¶
But calling _scope
of a component, then passing the component as an argument
is not trivial, is it? In fact, by design, this is meant only for some inter-component
methods.
Component methods can be defined within a controller class through the Component special class:
class FldCtrl(DOMScope):
_name = 'fld-ctrl'
class Component(object):
def check(self):
assert self.value == 'ham'
In the previous example, this means that the ‘field’ component would be decorated
with that extra check()
method.
Rules are:
- the special class must be called Component
- that class should derive from object (but
ComponentProxy
is also allowed) - the component will still be a ComponentProxy , will never really inherit that special Component class
- methods of the Component class will be bound to the ComponentProxy, hence
self
will refer to the ComponentProxy instance - Component may also have properties, static methods
- private members are not copied to the ComponentProxy. They are discarded.
- that applies to
__init__()
too. Components are volatile, they have no ordinary object lifecycle - if
MyScope
were to define a Component class, this would not apply to the “fld” component - but, if ‘fld-ctrl’ class is overriden, and the overriding class also defines a Component subclass, then both will have their members decorating “fld” component.
Magic filters¶
Locating a particular row in some long list (table) of data would be very inefficient with manners. Reason is, the lazy way manners build/discover sub-components and attributes.
Consider the following snippet:
table = context.cur_page['section']['story']['haystack']
for row in table['rows']:
if row['col_4'].text == 'needle':
needle_row = row
break
else:
raise CAssertionError("Cannot find needle in haystack",
component=table)
Manners up to v0.11 would need to iterate over all rows, and for each one of those compute all columns (up to ‘col_4’), then go back and retrieve the ‘text’ attribute of that element, to compare with ‘needle’. Each of those steps would involve 1-3 selenium commands to achieve. It would take a few seconds (depending on the position of that row) to reach there.
Magic filters can provide a much faster solution to this case.
Assuming that code can be refactored as:
table = context.cur_page['section']['story']['haystack']
for row in table['rows'].filter(lambda r: r['col_4'].text == 'needle'):
needle_row = row
break
else:
raise CAssertionError("Cannot find needle in haystack",
component=table)
then this loop could be resolved in as little as one Selenium call. See, magic filters operate on that lambda function and can reduce it to an XPath locator, before rows of the table would be iterated over. Thus, the expensive part is pushed from Python (manners) up to the browser (XPath) to locate and match.
Not all operators are currently supported in that lambda. It /could/ even be a named function or object method. But can only refer to sub-items using [], attributes and equality checks. This is work in progress.
Note:: in situations like the above example, if ‘rows’ were to be called like row_%d after their position, .filter() would never get that number right, since it locates the desired one by skipping any non-matching ones (but cannot count the skipped ones).
manners API: modules¶
behave_manners package¶
Subpackages¶
behave_manners.pagelems package¶
These classes represent a map of the site/page to be scanned/tested
They are the /definition/ describing interesting parts of the target page, not the elements of that page.
Submodules¶
-
class
BaseDPOParser
(root_element)¶ Bases:
html.parser.HTMLParser
,object
-
close
()¶ Handle any buffered data.
-
get_result
()¶
-
handle_charref
(name)¶
-
handle_comment
(data)¶ Comments are ignored
-
handle_data
(data)¶ Reject any non-whitespace text in elements
-
handle_decl
(decl)¶
-
handle_endtag
(tag)¶
-
handle_entityref
(name)¶
-
handle_pi
(data)¶
-
handle_starttag
(tag, attrs)¶
-
logger
= None¶
-
unknown_decl
(data)¶
-
-
class
DBaseLinkElement
(tag, attrs)¶ Bases:
behave_manners.pagelems.base_parsers.DPageElement
Baseclass for <link> elements in any pagelem document
Links have mandatory “rel”, “href” attributes and optional “url”, “title”.
-
consume
(element)¶
-
is_empty
= True¶
-
reduce
(site=None)¶ Cleanup internally, possibly merging nested elements
None can be returned, in case this element is no longer useful. reduce() shall be called after all children have been scanned and appended to this; after thet have been reduced.
If site is provided, this should be a context object, which provides (or consumes) additional resources from this element.
-
-
class
DOMScope
(parent, templates=None)¶ Bases:
object
A simple holder of shared components or variables across DOM levels
-
class
Component
¶ Bases:
object
-
clear
()¶ Clears the text if it’s a text entry element.
-
click
()¶ Clicks the element.
-
get_attribute
(name)¶ Gets the given attribute or property of the element.
This method will first try to return the value of a property with the given name. If a property with that name doesn’t exist, it returns the value of the attribute with the same name. If there’s no attribute with that name,
None
is returned.Values which are considered truthy, that is equals “true” or “false”, are returned as booleans. All other non-
None
values are returned as strings. For attributes or properties which do not exist,None
is returned.Args: - name - Name of the attribute/property to retrieve.
Example:
# Check if the "active" CSS class is applied to an element. is_active = "active" in target_element.get_attribute("class")
-
get_property
(name)¶ Gets the given property of the element.
Args: - name - Name of the property to retrieve.
Usage: text_length = target_element.get_property("text_length")
-
is_displayed
()¶ Whether the element is visible to a user.
-
is_enabled
()¶ Returns whether the element is enabled.
-
is_selected
()¶ Returns whether the element is selected.
Can be used to check if a checkbox or radio button is selected.
-
send_keys
(*value)¶ Simulates typing into the element.
Args: - value - A string for typing, or setting form fields. For setting file inputs, this could be a local file path.
Use this to send simple key events or to fill out form fields:
form_textfield = driver.find_element_by_name('username') form_textfield.send_keys("admin")
This can also be used to set file inputs.
file_input = driver.find_element_by_name('profilePic') file_input.send_keys("path/to/profilepic.gif") # Generally it's better to wrap the file path in one of the methods # in os.path to return the actual path to support cross OS testing. # file_input.send_keys(os.path.abspath("path/to/profilepic.gif"))
-
submit
()¶ Submits a form.
-
-
child
()¶ Return a child scope linked to this one
-
get_template
(key)¶
-
root_component
¶
-
take_component
(comp)¶
-
timeouts
= {}¶
-
wait_js_conditions
= []¶
-
class
-
class
DPageElement
(tag=None, attrs=())¶ Bases:
object
Base class for elements scanned in pagetemplate html
This is an abstract class, only subclasses shall be instantiated.
-
consume
(element)¶
-
is_empty
= False¶
-
iter_attrs
(webelem=None, scope=None, xpath_prefix='')¶ Iterate names of possible attributes
returns iterator of (name, descriptor)
-
iter_items
(remote, scope, xpath_prefix='', match=None)¶ Iterate possible children components
Parameters: - remote – remote WebElement to work on
- scope – DOMScope under which to operate
- xpath_prefix – hanging xpath from parent element
- match – if provided, only look for those items currently only a string is expected in match
Returns: tuple (name, welem, ptmpl, scope)
-
pretty_dom
()¶ Walk this template, generate (indent, name, xpath) sets of each node
Used for debugging, pretty-printing of parsed structure
-
reduce
(site=None)¶ Cleanup internally, possibly merging nested elements
None can be returned, in case this element is no longer useful. reduce() shall be called after all children have been scanned and appended to this; after thet have been reduced.
If site is provided, this should be a context object, which provides (or consumes) additional resources from this element.
-
tag
= ''¶
-
xpath
¶
-
xpath_locator
(score, top=False)¶ Return xpath locator of this and any child elements
Parameters: - score – mutable Integer, decrement depending on precision of locators
- top – locate exactly this (the top) element, or else any of its children
Returns: str
-
-
class
DataElement
(data)¶ Bases:
behave_manners.pagelems.base_parsers.DPageElement
-
append
(other)¶
-
consume
(element)¶
-
is_empty
= True¶
-
set_full
(full)¶
-
xpath_locator
(score, top=False)¶ Return xpath locator of this and any child elements
Parameters: - score – mutable Integer, decrement depending on precision of locators
- top – locate exactly this (the top) element, or else any of its children
Returns: str
-
Proxies of selenium WebElements into abstract structure of page’s information
Goal of the PageTemplates/Component is to reduce the multi-level DOM structure of a web-page to a compact structure of /Components/ , which then convey the semantic information of that DOM and are easy to assert in testing. Components may be trivial, corresponding to single DOM elements, or not, being abstractions over complex DOM structures. They /should/ have their entry point mapped to a particular DOM element.
Components must have a well-defined /path/ to be addressed with. This cannot be guaranteed by this framework, but rather should be achieved through careful design of the page templates. Heavily dependant on the web-page’s structure and technology. A good path is one that would map the “same” DOM element to a specific path, through subsequent readings of the webpage. Even if the webpage’s DOM has been re-built by the JS framework of the page (React, Angular, etc.) .
Example: a table. Using a <pe-repeat> template element, rows of that table could be mapped to components. For static tables, path could just be the row number of those rows. But this becomes non-deterministic for tables that are, say, grids with dynamic sorting or infinite scroll. On those, key to the rows should be some primary key of the row data, like the remote database ID or unique name of that row-data.
Components should be considered volatile. By design, this framewor does NOT hold a reference of children components to one, but rather re-maps children on demand. Likewise, values of attributes shall always be fetched from the remote, each time the Component attribute is read. No caching. It is the caller’s responsibility to copy the Component attributes to some other variable, if caching (rather than multiple WebDriver requests) is desired.
Unreachable components should be handled graceously. They would still raise an exception all the way up, but plugins may help in debugging, like by highlighting the visual position in the webpage where something is missing.
-
class
ComponentProxy
(name, parent, pagetmpl, webelem, scope)¶ Bases:
behave_manners.pagelems.dom_components._SomeProxy
Cross-breed of a Selenium element and DPO page object
-
component_name
¶
-
filter
(clause, safe=True)¶ Iterator over sub-components that satisfy a condition
Usage:
for row in table.filter(lambda r: r['col_4'].text == "bingo!"): print("Found the bingo row:", row)
equivalent to:
for row in table.values(): if row['col_4'].text == "bingo!": print("Found the bingo row:", row)
clause must be a function, which evaluates against a component and returns True whenever that component should participate in the result.
IFF clause is simple enough, filter() may optimize it to resolve the iteration in a very efficient search.
-
filter_gen
(clause, safe=True)¶ Generator of .filter() functions
Just because the optimization in .filter() may be expensive to compute, it can be done once (offline) and re-applied multiple times to generate many iterator instances
-
path
¶
-
-
class
DOM_Meta
¶ Bases:
f3utils.service_meta._ServiceMeta
Metaclass for DOMScope ones.
Prepares a dictionary of descriptors that the scope can apply to components or pages under it. Operates at class initialization phase,
-
exception
CAssertionError
(arg, component=None)¶ Bases:
AssertionError
,behave_manners.pagelems.exceptions.ComponentException
-
exception
CAttributeError
(arg, msg=None, component=None)¶ Bases:
AttributeError
,behave_manners.pagelems.exceptions.ComponentException
-
exception
CAttributeNoElementError
(arg, msg=None, component=None)¶ Bases:
behave_manners.pagelems.exceptions.CAttributeError
Raised when component attribute cannot be retrieved because of missing element
Special case of CAttributeError, may need to be handled explicitly. Implies NoSuchElementException from remote DOM.
-
exception
CKeyError
(arg, msg=None, component=None)¶ Bases:
KeyError
,behave_manners.pagelems.exceptions.ComponentException
-
exception
CValueError
(arg, component=None)¶ Bases:
ValueError
,behave_manners.pagelems.exceptions.ComponentException
-
class
ComponentException
(component=None, msg=None)¶ Bases:
object
Mixin for exceptions that can refer to faulty component
The component would be an instance of ComponentProxy
-
exception
ElementNotFound
(msg=None, screen=None, stacktrace=None, parent=None, selector=None, pagelem_name=None)¶ Bases:
selenium.common.exceptions.NoSuchElementException
Raised when a WebElement cannot locate a child, refers to that parent
-
pretty_parent
¶ String of parent element, in pretty format
-
-
exception
PageNotReady
¶ Bases:
AssertionError
Raised when browser has not finished rendering/settled the page
-
exception
Timeout
¶ Bases:
AssertionError
Raised when any action or element fails to respond in time
-
exception
UnwantedElement
(msg=None, screen=None, stacktrace=None)¶ Bases:
selenium.common.exceptions.NoSuchElementException
-
class
Integer
(value)¶ Bases:
object
Mutable integer implementation
Useful for counters, that need to be passed by reference
-
class
XPath
(xpath, complete=True)¶ Bases:
object
Dummy class to wrap an xpath string
Attribute complete: mark if this XPath should reliably locate elements or have less reliable results.
-
count_calls
(fn)¶ Wrapper for function, keeping a count of fn’s calls
-
prepend_xpath
(pre, xpath, glue=False)¶ Prepend some xpath to another, properly joining the slashes
-
textescape
(tstr)¶
-
to_bool
(v)¶ Convert boolean-like value of html attribute to python True/False
- Example truthy values (for attr in <b> ):
- <b attr> <b attr=”1”> <b attr=”true”> <b attr=”anything”>
- example falsy values:
- <b attr=”0”> <b attr=”false”> <b attr=”False”>
-
class
IHeadObject
(tag=None, attrs=())¶ Bases:
behave_manners.pagelems.index_elems.ISomeObject
-
reduce
(site=None)¶ Cleanup internally, possibly merging nested elements
None can be returned, in case this element is no longer useful. reduce() shall be called after all children have been scanned and appended to this; after thet have been reduced.
If site is provided, this should be a context object, which provides (or consumes) additional resources from this element.
-
-
class
IHtmlObject
(tag=None, attrs=())¶ Bases:
behave_manners.pagelems.index_elems.ISomeObject
Consume the <html> element as top-level index page
-
class
ILinkObject
(tag, attrs)¶ Bases:
behave_manners.pagelems.base_parsers.DBaseLinkElement
-
class
ISomeObject
(tag=None, attrs=())¶ Bases:
behave_manners.pagelems.base_parsers.DPageElement
Base class for ‘index.html’ and its sub-elements
These override reduce() so that <link> elements are consumed and the rest is discarded.
-
reduce
(site=None)¶ Cleanup internally, possibly merging nested elements
None can be returned, in case this element is no longer useful. reduce() shall be called after all children have been scanned and appended to this; after thet have been reduced.
If site is provided, this should be a context object, which provides (or consumes) additional resources from this element.
-
-
class
IndexHTMLParser
(site_collection)¶ Bases:
behave_manners.pagelems.base_parsers.BaseDPOParser
Parser only for ‘index.html’, containing links to other pages
In this pseydo-HTML site language, ‘index.html’ is only allowed to contain
<link>
elements to other named page object files. This parser restricts any other HTML elements for this file.Example:
<html> <link rel="next" href="main-page.html" title="Main Page" url="/"> <link rel="preload" href="common-components.html"> </html>
-
handle_starttag
(tag, attrs)¶
-
logger
= <Logger behave_manners.pagelems.index_elems.IndexHTMLParser (WARNING)>¶
-
-
class
BaseLoader
¶ Bases:
object
Abstract base for loading DPO html files off some storage
Subclass this to enable loading from any kind of storage.
-
multi_open
(filepattern, directory='', mode='rb')¶ Open a set of files (by glob pattern) for reading
Returns: iterator of open files
-
open
(fname, directory='', mode='rb')¶ Open file at fname path for reading.
Return a context manager file object
-
-
class
FSLoader
(root_dir)¶ Bases:
behave_manners.pagelems.loaders.BaseLoader
Trivial filesystem-based loader of files
-
multi_open
(filepattern, mode='rb')¶ Open a set of files (by glob pattern) for reading
Returns: iterator of open files
-
open
(fname, mode='rb')¶ Open file at fname path for reading.
Return a context manager file object
-
-
cmdline_main
()¶ when sun as a script, this behaves like a syntax checker for DPO files
-
class
DomContainerElement
(tag=None, attrs=())¶ Base class for ‘regular’ DOM elements that can contain others
-
reduce
(site=None)¶ Cleanup internally, possibly merging nested elements
None can be returned, in case this element is no longer useful. reduce() shall be called after all children have been scanned and appended to this; after thet have been reduced.
If site is provided, this should be a context object, which provides (or consumes) additional resources from this element.
-
-
class
LeafElement
(tag, attrs)¶ Generic element, that has no sub-elements
-
class
NamedElement
(tag, attrs)¶ Generic element, defining DOM component through ‘this’ attribute
-
pretty_dom
()¶ Walk this template, generate (indent, name, xpath) sets of each node
-
-
class
Text2AttrElement
(name, strip=False)¶ Internal pagelem node that retrieves text as an attribute to DOM component
This is instantiated when template has eg. <div>[text]</div> . The text is not asserted, but rather appointed to an attribute of parent component.
-
consume_after
(element)¶ Turn this into a partial text matcher, after some element tag
-
consume_before
(element)¶ Turn this into a partial text matcher, before some tag
-
-
class
Angular5App
(parent, templates=None)¶ Bases:
behave_manners.pagelems.scopes.WaitScope
Scope of an application using Angular 5+
-
wait_js_conditions
= ["if (document.readyState != 'complete') { return 'document'; }", "if (window.jQuery && window.jQuery.active) { return 'jQuery'; }", "if(!window.getAllAngularTestabilities) { return 'angular-startup';}", "if(window.getAllAngularTestabilities().findIndex(function(x) { return !x.isStable(); }) >= 0) {return 'angular';}"]¶
-
-
class
AngularJSApp
(parent, templates=None)¶ Bases:
behave_manners.pagelems.scopes.WaitScope
Scope of an application using AngularJS (1.x)
-
wait_js_conditions
= ["if (document.readyState != 'complete') { return 'document'; }", "if (window.jQuery && window.jQuery.active) { return 'jQuery'; }", "if (!angular) { return 'angularjs-startup'; }", "if (angular.element(document).injector().get('$http').pendingRequests.length > 0) { return 'angularjs';}"]¶
-
-
class
GenericPageScope
(parent, templates=None)¶ Bases:
behave_manners.pagelems.scopes.WaitScope
Default page scope
Page scope is the one attached to the remote DOM ‘page’, ie the <html> element. A good place to define page-wide properties, such as waiting methods.
Wait for JS at least
-
class
RootDOMScope
(templates=None, site_config=None)¶ Bases:
behave_manners.pagelems.base_parsers.DOMScope
Default scope to be used as parent of all scopes
Such one would be the parent of a ‘page’ scope.
Given that scopes can use the attributes of parent ones, this is where global attributes (such as site-wide config) can be set.
-
component_class
¶ alias of
behave_manners.pagelems.dom_components.ComponentProxy
-
-
class
WaitScope
(parent, templates=None)¶ Bases:
behave_manners.pagelems.base_parsers.DOMScope
-
isready_all
(driver)¶ Signal that all actions are settled, page is ready to contine
Override this to add any custom logic besides isready_js() condition.
-
isready_js
(driver)¶ One-off check that JS is settled
Parameters: driver – WebDriver instance
-
resolve_timeout
(timeout)¶
-
timeouts
= {'long': 60.0, 'medium': 10.0, 'short': 2.0}¶
-
wait
(timeout='short', ready_fn=None, welem=None, webdriver=None)¶ Waits until ‘ready_fn()` signals completion, times out otherwise
-
wait_all
(timeout='short', welem=None, webdriver=None)¶ Waits for all conditions of isready_all()
-
wait_js_conditions
= ["if (document.readyState != 'complete') { return 'document'; }", "if (window.jQuery && window.jQuery.active) { return 'jQuery'; }"]¶
-
-
class
DSiteCollection
(loader, config=None)¶ Bases:
behave_manners.pagelems.base_parsers.DPageElement
Collection of several HTML pages, like a site
Not supposed to be parsed, but directly instantiated as root hierarchy of all parsed html pageobject files.
-
consume
(element)¶
-
get_by_file
(fname)¶ Get page by template filename
Returns: pageelem
-
get_by_title
(title)¶ Get page by set title
Titles are arbitrary, pretty names assigned to page templates :return: (pageelem, url)
-
get_by_url
(url, fragment=None)¶ Find the page template that matches url (path) of browser
returns (page, title, params)
-
get_root_scope
()¶ Return new DOMScope bound to self
A DSiteCollection can be reused across runs, but ideally each run should start with a new scope, as returned by this method. Then, this scope should be passed to pages under this site, as those returned by get_by_title() , get_by_url() etc.
-
load_all
()¶ Load all referenced pages
Used for forced scan of their content
-
load_galleryfile
(pname)¶
-
load_index
(pname)¶
-
load_pagefile
(pname)¶
-
load_preloads
()¶ Load pending preloads or gallery files
-
logger
= <Logger site_collection (WARNING)>¶
-
register_link
(link)¶
-
Submodules¶
behave_manners.action_chains module¶
Wrapper around selenium.common.action_chains that uses domComponents as inputs
behave_manners.context module¶
behave_manners.dpo_run_browser module¶
Standalone runner of ‘DPO’ structures, validate against an externally loaded Selenium WebDriver instance.
-
cmdline_main
()¶ when sun as a script, this behaves like a syntax checker for DPO files
behave_manners.dpo_validator module¶
Standalone runner of ‘DPO’ structures, validate against an externally loaded Selenium WebDriver instance.
-
class
ExistingRemote
(command_executor, session_id, saved_capabilities, desired_capabilities={}, saved_w3c=None, **kwargs)¶ Bases:
selenium.webdriver.remote.webdriver.WebDriver
Remote webdriver that attaches to existing session
-
start_session
(desired_capabilities, browser_profile=None)¶ Creates a new session with the desired capabilities.
Args: - browser_name - The name of the browser to request.
- version - Which browser version to request.
- platform - Which platform to request the browser on.
- javascript_enabled - Whether the new session should support JavaScript.
- browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested.
-
-
cmdline_main
()¶ when sun as a script, this behaves like a syntax checker for DPO files
-
import_step_modules
(paths, modules)¶ Import any python module under ‘paths’, store its names in ‘modules/xx’
This is used to implicitly register ServiceMeta classes defined in these step modules.
-
shorten_txt
(txt, maxlen)¶
behave_manners.screenshots module¶
Utilities for browser screenshots, connected to events
-
class
Camera
(base_dir='.')¶ Bases:
object
A camera takes screenshots (or element shots) of the browser view
An instance will be attached to the behave context. Configured by site configuration. Then triggered each time a step explicitly wants a screenshot, or the hooks implicitly catching some failure.
-
capture_missing_elem
(context, parent, missing_path)¶ Screenshot of browser when some element is missing
Parameters: - context – behave Context containing site and browser
- parent – selenium.WebElement under which other was not found
- missing_path – string of XPath missing
-
highlight_element
(context, component=None, webelem=None, color=None, border=None)¶ Perform some action with an element visually highlighted
This should place a square rectangle on the DOM, around the element in question. The rectangle is appended into the root of the DOM to avoid any inheritance effects of the interesting element. After the action is performed, the highlight element is removed.
-
highlight_js
= "\n var highlight = document.createElement('div');\n highlight.setAttribute('style',\n 'border: {border}; ' +\n 'border-radius: 1px; ' +\n 'background-color: {color}; ' +\n 'z-index: 9999; ' +\n 'position: absolute; ' +\n 'left: {x}px; top: {y}px; ' +\n 'width: {width}px; height: {height}px;');\n document.body.appendChild(highlight);\n return highlight;\n "¶
-
snap_failure
(context, *args)¶
-
snap_success
(context, *args)¶
-
take_shot
(context, mode='')¶ Capture the full browser viewport.
-
behave_manners.site module¶
-
class
ChromeWebContext
(context, config=None)¶ Bases:
behave_manners.site.WebContext
Web context for Chromium browser
-
class
FakeContext
¶ Bases:
object
Dummy behave context
Will substitute a proper behave context for when manners stuff is run outside of a behave test suite.
-
add_cleanup
(fn, *args)¶
-
close
()¶
-
log
= <Logger context (WARNING)>¶
-
-
class
FirefoxWebContext
(context, config=None)¶ Bases:
behave_manners.site.WebContext
Web context for Chromium browser
-
class
GenericWebContext
(context, config=None)¶
-
class
IExploderWebContext
(context, config=None)¶ Bases:
behave_manners.site.WebContext
Web context for Chromium browser
-
exception
RemoteNetworkError
(message, **kwargs)¶
-
exception
RemoteSiteError
(message, **kwargs)¶ Bases:
Exception
Raised on errors detected at remote (browser) side
-
class
SiteContext
(context, config=None)¶ Bases:
object
Holds (web)site information in a behave context
- A SiteContext is attached to behave’s context like
- context.site = SiteContext(…)
and from there on tests can refer to site-wide attributes through that context.site object.
-
base_url
¶
-
init_collection
(loader=None)¶
-
class
WebContext
(context, config=None)¶ Bases:
behave_manners.site.SiteContext
Site context when a browser needs to be launched
-
fourOfours
= ('/favicon.ico',)¶
-
get_cur_title
(context)¶ Return pretty title of page currently loaded on the browser
-
launch_browser
(context)¶ Launch a browser, attach it to context.browser
Open a URL, by pretty title
-
process_logs
(context, consumer=None)¶ Fetch logs from browser and process them
Parameters: silent – suppress exceptions arising from logs
-
update_cur_page
(context)¶ Update context.cur_page when URL may have changed
-
validate_cur_page
(context, max_depth=10000)¶ Validates current browser page against pagelem template
Current page will be checked by url and the page template will be traversed all the way down.
-
-
current_context
()¶ Discover current behave context from caller stack
Use this inside an object that has no reference to behave context, but need to use it. Note that the context may be deliberately omitted, but in some exceptional cases would be useful to have.
Tutorial 1: Get started¶
A short tutorial on how to get started (from scratch) using behave_manners.
Note
The need to keep this tutorial simple and small contradicts with the core purpose of manners. Manners are designed for the complex case, for large sites, while the examples below are over-simplified. Please, keep that in mind. Gains from manners come when the site is much harder than this example.
1. Project setup¶
A minimal project for behave_manners should have the following files:
- environment.py
- Standard behave script for setting up configuration defaults
- config.yaml
- Recommended configuration file with project defaults, base settings.
- requirements.txt
- Recommended to have a file listing all python dependencies used for this project. Should contain a line for behave-manners
- site/index.html
- Required, mapping of URLs to template files & titles
- site/first-page.html
- At least one template file (to cover one or multiple site URLs)
- first-page.feature
- At least one feature file with scenarios
- steps/first_page_steps.py
- At least one python script with implementations of the feature steps
Having a setup.py is not really needed: think of the tests project as a collection of data files; they should be runable from a simple checkout.
Behave-manners should only need minimal content in the above files; tries to fill-in reasonable defaults for most un-mentioned values.
Examples of project files¶
environment.py
from behave_manners import site_setup
def before_all(context):
site_setup(context, config='config.yaml')
This is the only line needed for behave-manners to set-up everything: the browser, load the page templates (lazily) and set the base-url for all your site.
config.yaml
site:
base_url: https://material.angular.io
browser:
engine: chrome
launch_on: feature
screenshots:
dir: screenshots
page_objects:
index: site/index.html
As names suggest, contains settings for the site, browser tuning and points to the page templates that should be used.
requirements.txt
behave-manners
site/index.html
<html>
<head>
<link rel="next" href="first-page.html" title="Home Page" url="/">
<link rel="next" href="quickstart.html" title="Quick Start" url="/guide/getting-started">
</head>
</html>
index.html is a special file. Can only have a <head> containing <link> elements, to establish the mapping of URLs to the page templates and optionally titles. It is only a table; could have been in any other format, yet done in html for symmetry with the rest of page templates.
site/first-page.html
<html>
<body>
<material-docs-app ng-version="[ng-version]">
<div pe-deep class="docs-header-start">
<a this="get-started-button">
<span>Get started</span>
</a>
<pe-not>
<div class="bad-section">Foo
</div>
</pe-not>
</div>
</material-docs-app>
</body>
</html>
Each page template file should match the remote DOM of the site being tested. See documentation for further explanation of the page template format.
first-page.feature
Feature: Check home page
Scenario: Use the search form
Given I am at the "Home Page"
When I click get-started-button
Then I am directed to "Quick Start"
Feature files describe the desired tests in an abstract, high-level way.
steps/first_page_steps.py
from behave import given, when, then, step
@given(u'I am at the "{page}"')
def step_impl1(context, page):
context.site.navigate_by_title(context, page)
context.cur_element = context.cur_page
@when(u'I click {button}')
def click_a_button(self, button):
context.cur_element[button].click()
@then(u'I am directed to "{page}"')
def check_this_page(self, page):
title = context.site.update_cur_page(context)
assert title == page, "Currently at %s (%s)" % (title, context.browser.current_url)
Python implementations of steps is the ‘glue’ between features and the abstract Component tree that behave-manners can provide. Here, page elements (and nested sub-elements of) can be referenced like simple Python objects, also interacted with.
2. Python setup¶
Assuming that python is installed and operational, it is highly recommended that your project uses a dedicated virtual environment.
Within that virtualenv, only need to install behave-manners
. Or, even better,
call:
pip install -r requirements.txt
to cover any other dependencies your project may desire.
Chromedriver¶
The browser driver (chromedriver, here) needs to be installed separately, as a binary, into your system.
Calling which chromedriver
within the virtualenv should verify if it is
properly placed (and executable).
3. Verifying page templates¶
After the index.html and page templates are written, they can be tested independently of feature files (and step definitions). For this, behave-manners provides with a pair of utilities:
- behave-run-browser
- behave-validate-remote
Which are complementary: ‘run-browser’ will launch a browser with the settings as specified in ‘config.yaml’ . Then ‘validate-remote’ can be called repeatedly against that browser, to scan the page on that browser and print the Components that are discoverable in it.
Example from the above settings, in ‘https://material.angular.io’
$ behave-run-browser .
INFO:main:Entering main phase, waiting for browser to close
INFO:main:Browser changed to: Angular Material
...
$ behave-validate-remote
INFO:site_collection:Read index from 'site/index.html'
INFO:site_collection:Read page from 'site/first-page.html'
INFO:main:Got page First Page ()
<Page "https://material.angular.io/">
get-started-button <a class="docs-button mat-raised-button">
INFO:main:Validation finished, no errors
The standard output of the second is the Page component, and within it, that ‘get-started’ button. Real-life examples should be much more deep than that.
Tutorial 2: writing page elements¶
Step-by-step demonstration on how page elements work and get related to the target DOM.
Note
The example below includes code snippets from MDN site, by Mozilla Contributors and licensed under CC-BY-SA 2.5.
Note
MDN site’s HTML is too good for manners. It is well written, clean and structured. Manners can do much worse than that.
HTML vs HTML¶
Open MDN in the div element . Open the inspector to examine the DOM of that page.
The body of the page, contracted, looks like this:
<body data-slug="Web/HTML/Element/div" contextmenu="edit-history-menu" data-search-url="" class="document">
<script>... </script>
<ul id="nav-access">... </ul>
<header id="main-header" class="header-main">...</header>
<main id="content" role="main">...</main>
<footer id="nav-footer" class="nav-footer">...</footer>
</body>
And, focusing a part of the <main>
element we could see a snippet like:
<div id="toc" class="toc">
<div class="center">
<div class="toc-head">Jump to:</div>
<ol class="toc-links">
<li><a href="#Attributes" rel="internal" class="">Attributes</a></li>
<li><a href="#Usage_notes" rel="internal" class="">Usage notes</a></li>
<li><a href="#Examples" rel="internal" class="">Examples</a></li>
<li><a href="#Specifications" rel="internal" class="">Specifications</a></li>
<li><a href="#Browser_compatibility" rel="internal" class="">Browser compatibility</a></li>
<li><a href="#See_also" rel="internal" class="toc-current">See also</a>
</li></ol>
</div>
</div>
In principle, pasting the above section into a pagelem template file, should work. It would match 1:1 to the MDN site DOM, that page.
Removing redundant elements¶
Not all html elements are worth matching. Say, that <div class="center">
needs
not be that specific, nor the “Jump to:” text is any more specific than the id="toc"
of the outer <div>
So, the above snippet could be cleaned like:
<div id="toc">
<div>
<ol class="toc-links">
<li><a href="#Attributes">Attributes</a></li>
<li><a href="#Usage_notes">Usage notes</a></li>
<li><a href="#Examples">Examples</a></li>
<li><a href="#Specifications">Specifications</a></li>
<li><a href="#Browser_compatibility">Browser compatibility</a></li>
<li><a href="#See_also" class="toc-current">See also</a>
</li>
</ol>
</div>
</div>
Adding components¶
The above will not emit any matched components, until this
attributes are
set in the interesting elements:
<div id="toc" this="page-toc">
<div>
<ol class="toc-links" this="links">
<li this="Attributes"><a href="#Attributes">Attributes</a></li>
<li><a href="#Usage_notes">Usage notes</a></li>
<li><a href="#Examples">Examples</a></li>
<li this="Specifications"><a href="#Specifications">Specifications</a></li>
<li><a href="#Browser_compatibility">Browser compatibility</a></li>
<li><a href="#See_also" class="toc-current">See also</a>
</li>
</ol>
</div>
</div>
That would produce a structure of components like:
[page-toc]
- [links]
- [Attributes]
- [Specifications]
Generalizing¶
Then, the indidual <li>
elements should be covered all with one rule, rather
than hard-coding their exact attributes. This allows the same toc code to match
any table of contents that conforms to this layout.
Pagelem code now can be simplified like:
<div id="toc" this="page-toc">
<div>
<ol class="toc-links" this="links">
<li this="[title]"><a href="[href]">[title]</a></li>
</ol>
</div>
</div>
Getting simpler, and will also match all entries in the TOC this way.
href="[href]"
syntax means that the value of href=
attribute will be assigned
to a href
attribute of the resulting component. Will match anything in there.
Likewise [title]
as text of an element will copy whatever text into a title
attribute.
Then, this="[title]"
means that the value assigned to title
becomes the name
of the resulting component.
Thus, the resulting component structure after this generalisation should now be:
[page-toc]
- [links]
- [Attributes]
href = #Attributes
title = Attributes
- [Usage notes]
href = #Usage_notes
title = Usage notes
- [Examples]
href = #Examples
title = Examples
...
Full page¶
The above example cannot work standalone; rather it needs to be put in context of a full HTML page. Assuming the earlier structure, it would need to be written as:
<html>
<body>
<main id="content" role="main">
<div id="toc" this="page-toc">
<div>
<ol class="toc-links" this="links">
<li this="[title]"><a href="[href]">[title]</a></li>
</ol>
</div>
</div>
</main>
</body>
</html>
This works for the MDN case, because “toc” is just one level down from “main”. But would become worse if “toc” had been somewhere deep within the page DOM.
In that case, intermediate levels could be skipped with:
<html>
<body>
<div pe-deep id="toc" this="page-toc">
<ol pe-deep class="toc-links" this="links">
<li this="[title]"><a href="[href]">[title]</a></li>
</ol>
</div>
</body>
</html>
making code now more compact.
Templating¶
Since the TOC, footer and menu of this site are expected to be re-occuring across all pages, makes sense to write them as re-usable templates
gallery.html
<html>
<body>
<template id="toc">
<div pe-deep id="toc" this="page-toc">
<ol pe-deep class="toc-links" this="links">
<li this="[title]"><a href="[href]">[title]</a></li>
</ol>
</div>
</template>
<template id="header">
<header id="main-header" class="header-main">...</header>
</template>
<template id="footer">
<footer id="nav-footer" class="nav-footer">... </footer>
</template>
</body>
</html>
div-element.html
<html>
<head>
<link rel="import" href="gallery.html">
</head>
<body>
<use-template id="header"/>
<main id="content" role="main">
<use-template id="toc"/>
<!--custom code for catching the div-element page content -->
</main>
<use-template id="footer"/>
</body>
</html>
Templates may be called repeatedly in some page, even recursively.
Attribute matching¶
Attributes of pagelems will match same attributes on remote DOM, by default.
Example:
<div class="toc">
will match a div only if it’s class equals to “toc”. That’s not always convenient, since other classes may exist along “toc”, that matching should ignore.
<div class="+toc">
will then match if the div’s class contains “toc”.
Then, several values could be or-ed by mentioning them each:
<div role="document" role="article">
Writing title="[tooltip]"
will NOT attempt any match, but transfer the value
of remote DOM attribute title
to Component attribute .tooltip
. That can
co-exist with a matcher, like:
<div class="[class_]" class="+important">
Note here the underscore after class_
, because attribute names need to be
valid Python variable names, class
is a reserved word.
Attribute matching can be negated with the !
operator:
<div role="!contentinfo">
or, when the element should not contain a class:
<div class="!+hidden">
Other pagelems¶
The pagelem HTML-like language offers some other extra elements and attributes that help match remote DOM with less code on the testing side.
Components may be tagged as pe-optional
rather than failing the match. Pagelem
can match regardless of DOM tag with <pe-any>
element.
Alternate structures may be matched with <pe-choice>
.
Within a repetition or <pe-choice>
, collections of elements can be forced to
go together in a <pe-group>
.
More about them can be read in the reference and supplied examples.
Indices and tables¶
Built-in page elements Reference¶
Standard HTML elements¶
Some of the standard HTML elements need special handling, therefore they have corresponding classes defined in pagelems.
Otherwise, any other elements will be matched verbatim
-
class
DHtmlObject
(tag: <html>)¶ Consume the <html> element as top-level site page
As expected, may only have a <head> and a <body> .
-
class
DHeadElement
(tag: <head>)¶ HTML head only supports parsing-related elements
Currently, only <link>s and <template>s are allowed in <head>
-
class
DLinkObject
(tag: <link>)¶ Links are used to include other pagelem templates
-
class
DBodyElement
(tag: <body>)¶ Defines, matches the HTML <body>
-
class
ScriptElement
(tag: <script>)¶ Scripts are NOT allowed so far
-
class
InputElement
(tag: <input>)¶ Model an <input> element
Inputs are special and a bit weird. They are points of interaction with the remote side, also volatile, so must be exposed into DOM components. When this is specified, inputs become components as usual. Otherwise they become attributes of the parent components.
Therefore <input> elements MUST have this, an id or a name.
When ‘type’ is specified at this pagelem node, it helps choose the right descriptor class for the value of this input. If not, it may be auto-detected IF this is used OR name=’*’ performs wildcard detection of input elements.
If ‘pe-name’ attribute is specified, it overrides the ‘name’ one as the name to be used under the component, but does NOT need to match remote attribute(s).
Example:
<input pe-name="submit" type="submit">
will match any submit button and assign it to ‘submit’ name, even if the remote <input> element has no more attributes.
Note that setting both ‘this’ and ‘pe-name’ makes no sense, since a component-ized <input> element will not need a name.
-
class
TextAreaObj
(tag: <textarea>)¶ Textarea is handled just like <input>
-
class
GHtmlObject
(tag: <html> * in gallery)¶ Handle the <html> element of a gallery file
Gallery files can only contain <template> elements in their <head> or <body>, no other HTML.
Templates and slots¶
Templates and slots are defined in HTML5. This concept is re-used in pagelems, but as a means of parsing. See: Templates and Slots
-
class
DTemplateElement
(tag: <template>)¶ A template defines reusable DOM that is not normally rendered/scanned
See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
Likewise, the template will be re-used in this parser, match remote DOM and generate same proxy elements, under their caller.
-
id="name"
Mandatory attribute, needed to register this template in the local scope.
-
-
class
DUseTemplateElem
(tag: <use-template>)¶ Calls a <template> in its place
Example:
<use-template id="chapter"> <div slot="extra" class="remark" this="remark"> ... </div> </use-template>
-
id="name"
refers to a <template> to use
-
-
class
DSlotElement
(tag: <slot>)¶ The contents of a <slot> are replaced by parent scope, if available
This resembles the official W3C definition of slots in the browser, meant to be a placeholder for content that can be customized within a template.
-
name="slot-name"
Slots must have a name, so that implementations can attach to them.
-
-
class
DSlotContentElement
(tag: <pe-slotcontent>)¶ Jump back to the content of calling ‘<slot>’ element
Example:
<div class="slot"> <slot name="foo"> <div class="content"> </div> </slot> </div> <div slot="foo" class="bar"> <middle> <pe-slotcontent/> </middle> </div>
Should be equivalent to:
<div class="slot"> <div class="bar"> <middle> <div class="content"></div> </middle> </div> </div>
- This is inspired by Jinja2 ‘caller’ concept:
- http://jinja.pocoo.org/docs/2.10/templates/#call
Pagelem flow elements¶
-
class
DeepContainObj
(tag: <pe-deep>)¶ Match contained elements at any level deep inside the DOM
Example:
<body> <pe-deep> <div class="content"></div> </pe-deep> </body>
will match:
<body> <main> <div class="section"> <div class="content"> ... </div> </div> </main> </body>
Equivalent of using ‘pe-deep’ as an attribute.
Example (alt):
<body> <div pe-deep class="content"></div> </body>
-
class
RootAgainElem
(tag: <pe-root>)¶ Reset to root element (of DOM), keep component deep in tree
Useful for overlays, where the modal part is attached to the <body> instead of that element itself, but logically linked to that inner component.
-
class
RepeatObj
(tag: <pe-repeat>)¶ Locate multiple components, from one template
In its simpler form, it allows explicit iterations, where the inner component wouldn’t.
Example:
<pe-repeat> <div class="chapter" this="chapter> ...</div> </pe-repeat>
this would match all <div class=”chapter”> , assigning them to components like chapter1 , chapter2 etc.
But also useful for any controlled iteration. Supports
min
andmax
limits of how many components to match.Then, <pe-repeat> can have
this
attribute, assigning the list of produced components as one component. This is useful even when iteration is not desired, rather a component that does not consume its corresponding DOM (parent) element.
-
class
PeChoiceElement
(tag: <pe-choice>)¶ Matches the first child of this element (at least)
Using pe-choice means that at least one of its children shall match the remote, any others can fail.
Example:
<pe-choice> <div class="chapter" this="chapter">...</div> <div class="footer" this="footer">...</div> <section class="index" this="index"> ... </section> </pe-choice>
the above would succeed if any of these three patterns match, but could also emit /all/ of them, if these can be found.
Note that the output of <pe-choice> is ordered by the options given inside pe-choice, NOT the order that elements are in the remote DOM. This is due to the XPath implementation.
-
class
PeGroupElement
(tag: <pe-group>)¶ Trivial group, DOM-less container of many elements
Implements all-or-nothing logic, will never produce partial components* from its childen.
By default, unordered behaviour. Can specify ‘ordered’ to enforce that children sub-elements are matched in strict order.
* unless the group’s children have optional logic with <pe-choice> or <pe-repeat>.
-
class
PeMatchIDElement
(tag: <pe-matchid>)¶ Jump to an element that matches by id
It is a compact shorthand for taking an element’s attribute, resolving it and then searching the root of the DOM for that id.
Example:
<input this="input" role="combobox" aria-owns='[owns]'> <pe-matchid id="root['input'].owns" this="panel" pe-optional> ... </pe-matchid>
would resolve the aria-owns attribute of the <input> , then look for any element with that id in the tree, assign it to the dropdown panel.
Pagelem match elements¶
-
class
AnyElement
(tag: <pe-any>)¶ Match any HTML element
This would match any element in the remote DOM, but also serves as a baseclass for matching particular tags.
Useful for matching a particular element by attribute, rather than HTML tag.
Offers some standard attributes and rich syntax for matching remote DOM element properties.
-
this="name"
Exposes the element as a component under that name
-
slot="name"
Attaches this element into a <slot> of a <template>
Only valid within
<use-template>
blocks
-
pe-deep
Matches that element at any nested level under the parent. Equivalent of puting that element within a <pe-deep> tag
-
pe-optional
Makes matching optional, this element and any children may not exist.
-
pe-controller="some.controller"
Uses some.controller to complement functionality of this component.
-
pe-ctrl
Synonym of
pe-controller
-
-
class
PeNotElement
(tag: <pe-not>)¶ Negative-match element: check that element does NOT contain others
Negative-match will invert the meaning of contained matches, thus not selecting parents that contain specified patterns.
Example:
<div class="eggs"> <pe-not><div class="spam"></div> </pe-not> </div>
Meaning that it will match a div@class=eggs that does NOT contain a spam div .
When multiple children elements are specified inside pe-not, then all of them should be present for parent to mis-match. Having any of the children but not all, will allow the parent to match.
The other logic, failing the parent if any children exist, is possible by using multiple <pe-not> elements.
Do not use named elements (with this) or logic of pe-choice, pe-repeat or pe-optional elements inside a pe-not. As it will never create components, such logic is pointless.
-
class
RegexElement
(tag: <pe-regex>)¶ Match text of remote DOM element, parse with regex into attribute(s)
If this regex contains
(?P<name>...)
named groups, these will be exposed as name attributes.Otherwise, if this attribute is defined, expose all matched string under this name.
Note
text inside this element can contain ‘<’ and ‘>’, no need to escape these.
Example:
<div class="header" this="chapter"> <pe-regex>Chapter (?P<number>[0-9]+): (?P<title>.*)</pe-regex> </div>
This one would match DOM like this:
<div class="header">Chapter 4: Getting there</div>
and produce a component like:
chapter: number="4" title="Getting there"
Pagelem data elements¶
-
class
PeDataElement
(tag: <pe-data>)¶ Define arbitrary data as a component attribute
This supports two modes: with a value= attribute or using inner JSON data.
When using the value= attribute, the data will be a plain string. When using inner JSON, it can be any of the simple types that JSON supports.
Example:
<div class="chapter" this="chapter"> <pe-data name="lang" value="en"/> <pe-data name="difficulty">0.8</pe-data> ... </div>
- would produce a component like:
- chapter: lang=”en” difficulty=0.8
Note that difficulty is parsed as JSON, and therefore becomes a native float, rather than a string.
-
class
PEScopeDataElement
(tag: <pe-scopedata>)¶ Attach arbitrary data as a scope attribute
WARNING: this data will only work if the component containing this tag is ‘discovered’, ie. attached to that scope.