def test_transitions_to_parameterised_views_error(web_fixture): """If an Event triggers a Transition to a parameterised View, and it was not bound to the arguments expected by the target View, an error is raised.""" class ModelObject: @exposed def events(self, events): events.an_event = Event(label='Click me') model_object = ModelObject() class FormWithIncorrectButtonToParameterisedView(Form): def __init__(self, view): super().__init__(view, 'test_events') self.add_child( ButtonInput( self, model_object.events.an_event.with_arguments(arg1='1', arg2='2'))) class ParameterisedView(UrlBoundView): def assemble(self, object_key=None): self.title = 'View for: %s' % object_key class UIWithParameterisedViews(UserInterface): def assemble(self): normal_view = self.define_view('/static', title='Static') normal_view.set_slot( 'main', FormWithIncorrectButtonToParameterisedView.factory()) parameterised_view = self.define_view( '/dynamic', view_class=ParameterisedView, object_key=Field(required=True)) self.define_transition(model_object.events.an_event, normal_view, parameterised_view) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout(BasicPageLayout()) self.define_user_interface('/a_ui', UIWithParameterisedViews, IdentityDictionary(), name='test_ui') wsgi_app = web_fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) browser.open('/a_ui/static') with expected(ProgrammerError): browser.click(XPath.button_labelled('Click me'))
def parameterised_uis(self, fixture): """Sub UserInterfaces can also be parameterised by defining arguments in .define_user_interface, and receiving them in .assemble().""" class ParameterisedUserInterface(UserInterface): def assemble(self, ui_arg=None): if ui_arg == 'doesnotexist': raise CannotCreate() self.name = 'user_interface-%s' % ui_arg root = self.define_view('/aview', title='Simple user_interface %s' % self.name) root.set_slot('user_interface-slot', P.factory(text='in user_interface slot')) class UIWithParameterisedUserInterfaces(UserInterface): def assemble(self): self.define_user_interface('/parameterisedui', ParameterisedUserInterface, {'user_interface-slot': 'main'}, ui_arg=fixture.argument, name='paramui') class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout( PageLayout( contents_layout=ColumnLayout('main').with_slots())) self.define_user_interface('/a_ui', UIWithParameterisedUserInterfaces, IdentityDictionary(), name='myui') wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) if fixture.should_exist: browser.open(fixture.url) # The correct argument was passed vassert( browser.title == 'Simple user_interface user_interface-%s' % fixture.expected_value) # The slots of the sub-user_interface is correctly plugged into the page [p] = browser.lxml_html.xpath('//p') vassert(p.text == 'in user_interface slot') else: # When the URL cannot be mapped browser.open(fixture.url, status=404)
def test_handling_uncaught_exceptions(web_fixture): """If an uncaught exception is raised, the session is closed properly.""" @stubclass(ReahlWSGIApplication) class ReahlWSGIApplicationStub2(ReahlWSGIApplicationStub): def resource_for(self, request): raise AssertionError('this an unknown breakage') app = ReahlWSGIApplicationStub2(web_fixture.config) browser = Browser(app) with CallMonitor(app.system_control.finalise_session) as monitor: assert monitor.times_called == 0 with expected(AssertionError): browser.open('/') assert monitor.times_called == 1
def test_domain_exception(web_fixture, session_scope_fixture): """Typing the wrong password results in an error message being shown to the user.""" browser = Browser(web_fixture.new_wsgi_app(site_root=SessionScopeUI)) user = session_scope_fixture.user browser.open('/') browser.click(XPath.link().with_text('Log in')) browser.type(XPath.input_labelled('Email'), '*****@*****.**') browser.type(XPath.input_labelled('Password'), 'wrong password') browser.click(XPath.button_labelled('Log in')) assert browser.is_element_present( XPath.div().including_text('The email/password given do not match'))
def widgets_for_tasks(self, fixture): """The widget to use for displaying a particular type of task can be set via an entry point.""" pkg_resources.working_set.add(easter_egg) line = 'MyTaskWidget = reahl.domainui_dev.bootstrap.test_workflow:MyTaskWidget' easter_egg.add_entry_point_from_line('reahl.workflowui.task_widgets', line) with fixture.persistent_test_classes(MyTask): task = MyTask(queue=fixture.queue, title='a task') browser = Browser(fixture.wsgi_app) fixture.log_in(browser=browser) browser.open('/inbox/task/%s' % task.id) html = browser.get_html_for('//div/p') vassert(html == '<p>my task widget</p>')
def test_wrong_arguments_to_define_event_handler(web_fixture): """Passing anything other than an Event to define_event_handler is an error.""" fixture = web_fixture class MyForm(Form): def __init__(self, view, name): super(MyForm, self).__init__(view, name) self.define_event_handler(EmptyStub()) wsgi_app = fixture.new_wsgi_app(child_factory=MyForm.factory('form')) browser = Browser(wsgi_app) with expected(IsInstance): browser.open('/')
def test_basic_error2(web_fixture): """Sending the the wrong arguments for the specified class to define_page is reported to the programmer.""" class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page, 1, 2) self.define_view('/', title='Hello') fixture = web_fixture wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) with expected(IncorrectArgumentError, test='define_page was called with arguments that do not match those expected by.*'): browser.open('/')
def test_getting_view(web_fixture): """ONLY If a View is readable, it can be GET""" fixture = web_fixture def disallowed(): return False class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page) self.define_view('/view', 'Title', read_check=disallowed) wsgi_app = web_fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) browser.open('/view', status=403)
def basic_error3(fixture): """Forgetting to define either a page of a page for a View is reported to the programmer.""" class MainUI(UserInterface): def assemble(self): self.define_view('/', title='Hello') wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) def check_exc(ex): msg = six.text_type(ex) vassert(msg == 'there is no page defined for /') with expected(ProgrammerError, test=check_exc): browser.open('/')
def test_logging_in(web_fixture, session_scope_fixture): """A user can log in by going to the Log in page. The name of the currently logged in user is displayed on the home page.""" browser = Browser(web_fixture.new_wsgi_app(site_root=SessionScopeUI)) user = session_scope_fixture.user browser.open('/') browser.click(XPath.link().with_text('Log in')) browser.type(XPath.input_labelled('Email'), '*****@*****.**') browser.type(XPath.input_labelled('Password'), 'topsecret') browser.click(XPath.button_labelled('Log in')) browser.click(XPath.link().with_text('Home')) assert browser.is_element_present(XPath.paragraph().including_text('Welcome John Doe'))
def test_slot_error(web_fixture): """Supplying contents for a slot that does not exist results in s sensible error.""" class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout(BasicPageLayout()) home = self.define_view('/', title='Hello') home.set_slot('main', P.factory(text='Hello world')) home.set_slot('nonexistantslotname', P.factory(text='I am breaking')) fixture = web_fixture wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) with expected(ProgrammerError, test='An attempt was made to plug Widgets into the following slots that do not exist.*'): browser.open('/')
def guards(self, fixture): """Guards can be set on Transitions. A Transition is only elegible for firing if its guard is True.""" fixture.guard_value = None adjustable_guard = Action(lambda: fixture.guard_value) false_guard = Action(lambda: False) class UIWithGuardedTransitions(UserInterface): def assemble(self): event = Event(label='Click me') event.bind('anevent', None) slot_definitions = {'main': FormWithButton.factory(event)} viewa = self.define_view('/viewa', title='View a', slot_definitions=slot_definitions) viewb = self.define_view('/viewb', title='View b') viewc = self.define_view('/viewc', title='View c') self.define_transition(event, viewa, viewb, guard=false_guard) self.define_transition(event, viewa, viewc, guard=adjustable_guard) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout( PageLayout( contents_layout=ColumnLayout('main').with_slots())) self.define_user_interface('/a_ui', UIWithGuardedTransitions, IdentityDictionary(), name='test_ui') wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) # The transition with True guard is the one followed fixture.guard_value = True browser.open('/a_ui/viewa') browser.click('//input[@value="Click me"]') vassert(browser.location_path == '/a_ui/viewc') # If there is no Transition with a True guard, fail fixture.guard_value = False browser.open('/a_ui/viewa') with expected(ProgrammerError): browser.click('//input[@value="Click me"]')
def redirect_used_to_return(self, fixture): """A Return is an exception used with Preconditoins to return automatically to another View (as set by detour), instead of using a return_transition (the latter can only be triggered by a user).""" class UIWithDetour(UserInterface): def assemble(self): viewa = self.define_view('/viewa', title='View a') explicit_return_view = self.define_view( '/explicitReturnView', title='Explicit Return View') default = self.define_view('/defaultReturnView', title='Default view to return to') detour = self.define_view('/detour', title='Detour') viewa.add_precondition( ViewPreCondition( lambda: False, exception=Detour( detour.as_bookmark(self), return_to=explicit_return_view.as_bookmark(self)))) detour.add_precondition( ViewPreCondition(lambda: False, exception=Return( default.as_bookmark(self)))) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page) self.define_user_interface('/a_ui', UIWithDetour, IdentityDictionary(), name='test_ui') wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) # Normal operation - when a caller can be determined browser.open('/a_ui/viewa') vassert(browser.location_path == '/a_ui/explicitReturnView') # - the query string is cleared after such a return (it is used to remember where to return to) vassert(browser.location_query_string == '') # When a caller cannot be determined, the default is used browser.open('/a_ui/detour') vassert(browser.location_path == '/a_ui/defaultReturnView') # - the query string is cleared after such a return (it is used to remember where to return to) vassert(browser.location_query_string == '')
def test_ui_slots_map_error(web_fixture, user_interface_error_scenarios): class SimpleUserInterface(UserInterface): def assemble(self): root = self.define_view('/', title='View') root.set_slot('name', P.factory()) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page) self.define_user_interface('/a_ui', SimpleUserInterface, user_interface_error_scenarios.slot_map, name='test_ui') wsgi_app = web_fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) with expected(ProgrammerError): browser.open('/a_ui/')
def omitting_label(fixture): """The label will be rendered hidden (but available to screen readers) if this is explicity requested.""" class FormWithInputNoLabel(Form): def __init__(self, view): super(FormWithInputNoLabel, self).__init__(view, 'aform') self.use_layout(FormLayout()) self.layout.add_input(TextInput( self, fixture.domain_object.fields.an_attribute), hide_label=True) browser = Browser( fixture.new_wsgi_app(child_factory=FormWithInputNoLabel.factory())) browser.open('/') [label, help_text] = fixture.get_form_group_children(browser) vassert(label.tag == 'label') vassert(label.attrib['class'] == 'sr-only')
def test_pageflow1(web_fixture, pageflow1_scenario): browser = Browser(pageflow1_scenario.wsgi_app) browser.open('/') assert browser.is_element_present('//ul[contains(@class,"nav")]') browser.click(XPath.link().with_text('Add')) assert browser.current_url.path == '/add' browser.type(XPath.input_labelled('Name'), 'John') browser.type(XPath.input_labelled('Email'), '*****@*****.**') browser.click(XPath.button_labelled('Save')) assert browser.current_url.path == '/' assert browser.is_element_present( XPath.paragraph().including_text('John: [email protected]'))
def bookmarks_support_such_fragments(self, fixture): """Page-internal bookmarks support such bookmarkable widgets. These Bookmarks usually do not affect an URL - they just set widget states in different ways, depending on whether the client has javascript support or not. However, if a page was opened using the widget arguments on the querystring, a bookmark that would normally only have changed that argument on the hash will point to a new url on which the argument has been removed from the querystring and changed on the hash. """ internal_bookmark = Bookmark.for_widget( 'an ajax bookmark', query_arguments={'fancy_state': 2}) normal_bookmark = Bookmark('/', '', 'a normal bookmark') # You can query whether a bookmark is page_internal or not vassert(internal_bookmark.is_page_internal) vassert(not normal_bookmark.is_page_internal) # page-internal bookmarks must be added to normal ones to be useful usable_bookmark = normal_bookmark + internal_bookmark wsgi_app = fixture.new_wsgi_app( widget_factory=A.factory_from_bookmark(usable_bookmark)) # Case: when rendered without javascript browser = Browser(wsgi_app) browser.open('/') a = browser.lxml_html.xpath('//a')[0] vassert(a.attrib['href'] == '/?fancy_state=2') vassert(a.text == 'an ajax bookmark') # Case: when rendered in a browser with javascript support fixture.reahl_server.set_app(wsgi_app) fixture.driver_browser.open('/') vassert( fixture.driver_browser.is_element_present( "//a[@href='/#fancy_state=2']")) vassert(not fixture.driver_browser.is_element_present( "//a[@href='/?fancy_state=2']")) # Case: when the argument was given on the query string of the current page fixture.driver_browser.open('/?fancy_state=4') vassert( fixture.driver_browser.is_element_present( "//a[@href='/#fancy_state=2']"))
def test_pageflow2(fixture): browser = Browser(fixture.wsgi_app) browser.open('/') vassert(browser.is_element_present('//ul[contains(@class,"nav")]')) browser.click(XPath.link_with_text('Add an address')) vassert(browser.location_path == '/add') browser.type(XPath.input_labelled('Name'), 'John') browser.type(XPath.input_labelled('Email'), '*****@*****.**') browser.click(XPath.button_labelled('Save')) vassert(browser.location_path == '/') vassert( browser.is_element_present( XPath.paragraph_containing('John: [email protected]')))
def dynamic_sub_resources_factory_args(self, fixture): """Such dynamic SubResources can also be created with arguments specified to its Factory (instead of only from the path).""" @stubclass(SubResource) class ParameterisedSubResource(SubResource): sub_regex = 'dynamic_(?P<path_param>[^/]+)' sub_path_template = 'dynamic_%(path_param)s' def __init__(self, unique_name, factory_arg, factory_kwarg, path_param): super(ParameterisedSubResource, self).__init__(unique_name) self.path_param = path_param self.factory_arg = factory_arg self.factory_kwarg = factory_kwarg @exempt def handle_get(self, request): args = '%s|%s|%s' % (self.factory_arg, self.factory_kwarg, self.path_param) return Response(unicode_body=six.text_type(args)) @exempt @classmethod def create_resource(cls, unique_name, factory_arg, factory_kwarg=None, path_param=None): return cls(unique_name, factory_arg, factory_kwarg, path_param) @stubclass(Widget) class WidgetWithSubResource(Widget): def __init__(self, view): super(WidgetWithSubResource, self).__init__(view) factory = ParameterisedSubResource.factory( 'uniquename', {'path_param': Field(required=True)}, 'arg to factory', factory_kwarg='kwarg to factory') view.add_resource_factory(factory) wsgi_app = fixture.new_wsgi_app( view_slots={'main': WidgetWithSubResource.factory()}) browser = Browser(wsgi_app) browser.open('/__uniquename_dynamic_one') vassert(browser.raw_html == 'arg to factory|kwarg to factory|one')
def detours_and_explicit_return_view(self, fixture): """A Detour can also explicitly set the View to return to.""" class UIWithDetour(UserInterface): def assemble(self): event = Event(label='Click me') event.bind('anevent', None) viewa = self.define_view('/viewa', title='View a') explicit_return_view = self.define_view( '/explicitReturnView', title='Explicit Return View') slot_with_button = {'main': FormWithButton.factory(event)} detour = self.define_view('/detour', title='Detour', slot_definitions=slot_with_button) viewa.add_precondition( ViewPreCondition( lambda: False, exception=Detour( detour.as_bookmark(self), return_to=explicit_return_view.as_bookmark(self)))) self.define_return_transition(event, detour) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout( PageLayout( contents_layout=ColumnLayout('main').with_slots())) self.define_user_interface('/a_ui', UIWithDetour, IdentityDictionary(), name='test_ui') wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) browser.open('/a_ui/viewa') vassert(browser.location_path == '/a_ui/detour') browser.click('//input[@type="submit"]') vassert(browser.location_path == '/a_ui/explicitReturnView') # The query string is cleared after such a return (it is used to remember where to return to) vassert(browser.location_query_string == '')
def basic_transition(self, fixture): """Transitions express how the browser is ferried between Views in reaction to user-initiated Events.""" def do_something(): fixture.did_something = True class UIWithTwoViews(UserInterface): def assemble(self): event = Event(label='Click me', action=Action(do_something)) event.bind('anevent', None) slot_definitions = {'main': FormWithButton.factory(event)} viewa = self.define_view('/viewa', title='View a', slot_definitions=slot_definitions) viewb = self.define_view('/viewb', title='View b', slot_definitions=slot_definitions) self.define_transition(event, viewa, viewb) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout( PageLayout( contents_layout=ColumnLayout('main').with_slots())) self.define_user_interface('/a_ui', UIWithTwoViews, IdentityDictionary(), name='test_ui') wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) # The transition works from viewa fixture.did_something = False browser.open('/a_ui/viewa') browser.click('//input[@value="Click me"]') vassert(browser.location_path == '/a_ui/viewb') vassert(fixture.did_something) # The transition does not work from viewb fixture.did_something = False browser.open('/a_ui/viewb') with expected(ProgrammerError): browser.click('//input[@value="Click me"]') vassert(not fixture.did_something)
def test_specifying_help_text(web_fixture, form_layout_fixture): """You can optionally specify help_text when adding an input.""" fixture = form_layout_fixture class FormWithInputAndHelp(Form): def __init__(self, view): super().__init__(view, 'aform') self.use_layout(FormLayout()) self.layout.add_input(TextInput(self, fixture.domain_object.fields.an_attribute), help_text='some help') browser = Browser(web_fixture.new_wsgi_app(child_factory=FormWithInputAndHelp.factory())) browser.open('/') [label, input_widget, help_text] = fixture.get_form_group_children(browser) # form-group has help-text assert help_text.tag == 'p' assert 'text-muted' in help_text.attrib['class'] assert help_text.text == 'some help'
def basic_error2(fixture): """Sending the the wrong arguments for the specified class to define_page is reported to the programmer.""" class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page, 1, 2) self.define_view('/', title='Hello') wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) def check_exc(ex): msg = six.text_type(ex) vassert( msg.startswith( 'define_page was called with arguments that do not match those expected by' )) with expected(IncorrectArgumentError, test=check_exc): browser.open('/')
def immutable_remote_methods(self, fixture): """A RemoteMethod that is immutable is accessible via GET (instead of POST).""" def callable_object(): return 'value returned from method' remote_method = RemoteMethod('amethod', callable_object, MethodResult(), immutable=True) wsgi_app = fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) # GET, since the method is immutable browser.open('/_amethod_method') vassert(browser.raw_html == 'value returned from method') # POSTing to the URL, is not supported browser.post('/_amethod_method', {}, status=405)
def test_query_string_widget_arguments(web_fixture, value_scenarios): """Widgets can have arguments that are read from a query string""" fixture = value_scenarios class WidgetWithQueryArguments(Widget): def __init__(self, view): super().__init__(view) self.add_child(P(view, text=str(self.arg_directly_on_widget))) @exposed def query_fields(self, fields): fields.arg_directly_on_widget = fixture.field.unbound_copy() # We use a copy, because the Field on the fixture is only constructed once, but we get here more than once during a GET wsgi_app = web_fixture.new_wsgi_app(enable_js=True, child_factory=WidgetWithQueryArguments.factory()) browser = Browser(wsgi_app) browser.open('/?%s' % fixture.field_on_query_string.format(field_name='arg_directly_on_widget')) assert browser.lxml_html.xpath('//p')[0].text == str(fixture.field_value_marshalled)
def local_transition(self, fixture): """A local Transition has its source as its target.""" def do_something(): fixture.did_something = True fixture.guard_passes = True guard = Action(lambda: fixture.guard_passes) class UIWithAView(UserInterface): def assemble(self): event = Event(label='Click me', action=Action(do_something)) event.bind('anevent', None) slot_definitions = {'main': FormWithButton.factory(event)} viewa = self.define_view('/viewa', title='View a', slot_definitions=slot_definitions) self.define_local_transition(event, viewa, guard=guard) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout( PageLayout( contents_layout=ColumnLayout('main').with_slots())) self.define_user_interface('/a_ui', UIWithAView, IdentityDictionary(), name='test_ui') wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) # The transition works from viewa fixture.did_something = False browser.open('/a_ui/viewa') browser.click('//input[@value="Click me"]') vassert(browser.location_path == '/a_ui/viewa') vassert(fixture.did_something) # But it is also guarded fixture.guard_passes = False browser.open('/a_ui/viewa') with expected(ProgrammerError): browser.click('//input[@value="Click me"]')
def test_non_writable_input_is_dealt_with_like_invalid_input(web_fixture): """If a form submits a value for an Input that is linked to Field with access rights that prohibit writing, the input is silently ignored.""" fixture = web_fixture class ModelObject(object): field_name = 'Original value' @exposed def events(self, events): events.an_event = Event(label='click me') @exposed def fields(self, fields): fields.field_name = Field( default='abc', writable=Allowed(False), disallowed_message='you are not allowed to write this') model_object = ModelObject() class TestPanel(Div): def __init__(self, view): super(TestPanel, self).__init__(view) form = self.add_child(Form(view, 'some_form')) form.define_event_handler(model_object.events.an_event) form.add_child(ButtonInput(form, model_object.events.an_event)) form.add_child(TextInput(form, model_object.fields.field_name)) fixture.form = form wsgi_app = web_fixture.new_wsgi_app(child_factory=TestPanel.factory()) browser = Browser(wsgi_app) browser.open('/') browser.post( fixture.form.event_channel.get_url().path, { 'event.some_form-an_event?': '', 'field_name': 'illigitimate value', 'some_form-_reahl_client_concurrency_digest': '', 'some_form-_reahl_database_concurrency_digest': '' }) browser.follow_response() assert model_object.field_name == 'Original value'
def defaults_for_slots(self, fixture): """A Widget can have defaults for its slots.""" class MyPage(Widget): def __init__(self, view): super(MyPage, self).__init__(view) self.add_child(Slot(view, 'slot3')) self.add_default_slot('slot3', P.factory(text='default')) class MainUI(UserInterface): def assemble(self): self.define_page(MyPage) self.define_view('/', title='Home') wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) browser.open('/') [slot3_p] = browser.lxml_html.xpath('//p') vassert(slot3_p.text == 'default')
def test_inverse_view_preconditions(web_fixture): """A ViewPreCondition can give you another ViewPreCondition which it itself negated, optionally with its own exception.""" class SomeException(Exception): pass class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page) view = self.define_view('/', title='Hello') passing_precondition = ViewPreCondition(lambda: True) failing_precondition = passing_precondition.negated( exception=SomeException) view.add_precondition(failing_precondition) wsgi_app = web_fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) with expected(SomeException): browser.open('/')
def test_duplicate_forms(web_fixture): """It is an error to add more than one form with the same unique_name to a page.""" fixture = web_fixture class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout( BasicPageLayout(slots=['main', 'secondary'])) home = self.define_view('/', title='Home page') home.set_slot('main', Form.factory('myform')) home.set_slot('secondary', Form.factory('myform')) wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) with expected( ProgrammerError, test='More than one form was added using the same unique_name.*'): browser.open('/')