Personal tools
Skip to content. | Skip to navigation
Browser testing with splinter Splinter is a library which provides a common browser API with a driver for zope.testbrowser. The ftw.testing package provides integration of Splinter with Plone using Page Objects. For using the splinter features, use the splinter extras require: ftw.testing [splinter] Setting a package up for browser tests It’s easy to setup your package for browser tests: Add a test-dependency to ftw.testing in your setup.py: tests_require = [ 'ftw.testing[splinter]', ] setup(name='my.package', ... tests_require=tests_require, extras_require=dict(tests=tests_require), ) In your testing.py use the FunctionalSplinterTesting layer wrapper: from ftw.testing import FunctionalSplinterTesting from plone.app.testing import PLONE_FIXTURE from plone.app.testing import PloneSandboxLayer from plone.app.testing import applyProfile from zope.configuration import xmlconfig class MyPackageLayer(PloneSandboxLayer): defaultBases = (PLONE_FIXTURE,) def setUpZope(self, app, configurationContext): import my.package xmlconfig.file('configure.zcml', my.package) def setUpPloneSite(self, portal): applyProfile(portal, 'my.package:default') MY_PACKAGE_FIXTURE = MyPackageLayer() MY_PACKAGE_FUNCTIONAL_TESTING = FunctionalSplinterTesting( bases=(MY_PACKAGE_FIXTURE, ), name="my.package:functional") Write tests using the Plone Page Objects: from ftw.testing import browser from ftw.testing.pages import Plone from my.package.testing import MY_PACKAGE_FUNCTIONAL_TESTING from plone.app.testing import SITE_OWNER_NAME from plone.app.testing import SITE_OWNER_PASSWORD from unittest2 import TestCase class TestDocument(TestCase): layer = MY_PACKAGE_FUNCTIONAL_TESTING def test_add_document(self): Plone().login(SITE_OWNER_NAME, SITE_OWNER_PASSWORD) Plone().visit_portal() Plone().create_object('Page', {'Title': 'Foo', 'Body Text': '<b>Hello World</b>'}) self.assertTrue(browser().is_text_present('Hello World')) Writing Page Objects Write your own Page Objects for your views and content types. Put a module pages.py in your tests folder: from ftw.testing.pages import Plone class MyContentType(Plone): def create_my_content(self, title, text): self.create_object('MyContent', {'Title': title, 'Body Text': text}) return self The Page Object should have methods for all features of your view. Using the Plone Page Objects The Plone page object provided by ftw.testing already has the most important features built in, such as: portal_url handling Login Accessing Headings, <body>-CSS-classes, status messages Adding content TinyMCE handling Currently it’s best to just look in the page object code. MockTestCase ftw.testing provides an advanced MockTestCase which provides bases on the plone.mocktestcase MockTestCase. from ftw.testing import MockTestCase The following additional methods are available: self.providing_mock(interfaces, *args, **kwargs) Creates a mock which provides interfaces. self.mock_interface(interface, provides=None, *args, **kwargs) Creates a mock object implementing interface. The mock does not only provide interface, but also use it as specification and asserts that the mocked methods do exist on the interface. self.stub(*args, **kwargs) Creates a stub. It acts like a mock but has no assertions. self.providing_stub(interfaces, *args, **kwargs) Creates a stub which provides interfaces. self.stub_interface(interface, provides=None, *args, **kwargs) Does the same as mock_interface, but disables counting of expected method calls and attribute access. See “Mocking vs. stubbing” below. self.set_parent(context, parent_context) Stubs the context so that its acquisition parent is parent_context. Expects at least context to be a mock or a stub. Returns the context. self.stub_request(interfaces=[], stub_response=True, content_type='text/html', status=200) Returns a request stub which can be used for rendering templates. With the stub_response option, you can define if the request should stub a response by itself. The other optional arguments: content_type: Defines the expected output content type of the response. status: Defines the expected status code of the response. self.stub_response(request=None, content_type='text/html', status=200)) Returns a stub response with some headers and options. When a request is given the response is also added to the given request. The other optional arguments: content_type: Defines the expected output content type of the response. status: Defines the expected status code of the response. self.assertRaises(*args, **kwargs) Uses unittest2 implementation of assertRaises instead of unittest implementation. It also fixes a problem in mock_tool, where the getToolByName mock had assertions which is not very useful in some cases. Mocking vs. stubbing A mock is used for testing the communication between two objects. It asserts method calls. This is used when a test should not test if a object has a specific state after doing something (e.g. it has it’s attribute xy set to something), but if the object does something with another object. If for example an object Foo sends an email when method bar is called, we could mock the sendmail object and assert on the send-email method call. On the other hand we often have to test the state of an object (attribute values) after doing something. This can be done without mocks by just calling the method and asserting the attribute values. But then we have to set up an integration test and install plone, which takes very long. For testing an object with dependencies to other parts of plone in a unit test, we can use stubs for faking other (separately tested) parts of plone. Stubs work like mocks: you can “expect” a method call and define a result. The difference between stubs and mocks is that stubs do not assert the expectations, so there will be no errors if something expected does not happen. So when using stubs we can assert the state without asserting the communcation between objects. Component registry layer The MockTestCase is able to mock components (adapters, utilities). It cleans up the component registry after every test. But when we use a ZCML layer, loading the ZCML of the package it should use the same component registry for all tests on the same layer. The ComponentRegistryLayer is a layer superclass for sharing the component registry and speeding up tests. Usage: from ftw.testing.layer import ComponentRegistryLayer class ZCMLLayer(ComponentRegistryLayer): def setUp(self): super(ZCMLLayer, self).setUp() import my.package self.load_zcml_file('configure.zcml', my.package) ZCML_LAYER = ZCMLLayer() Be aware that ComponentRegistryLayer is a base class for creating your own layer (by subclassing ComponentRegistryLayer) and is not usable with defaultBases directly. This allows us to use the functions load_zcml_file and load_zcml_string. Mailing test helper The Mailing helper object mocks the mailhost and captures sent emails. The emails can then be easily used for assertions. Usage: from ftw.testing.mailing import Mailing import transaction class MyTest(TestCase): layer = MY_FUNCTIONAL_TESTING def setUp(self): Mailing(self.layer['portal']).set_up() transaction.commit() def tearDown(self): Mailing(self.layer['portal']).tear_down() def test_mail_stuff(self): portal = self.layer['portal'] do_send_email() mail = Mailing(portal).pop() self.assertEquals('Subject: ...', mail) Freezing datetime.now() When testing code which depends on the current time, it is necessary to set the current time to a specific time. The freeze context manager makes that really easy: from ftw.testing import freeze from datetime import datetime with freeze(datetime(2014, 5, 7, 12, 30)): The freeze context manager patches the datetime module, the time module and supports the Zope DateTime module. It removes the patches when exiting the context manager. Updating the freezed time from ftw.testing import freeze from datetime import datetime with freeze(datetime(2014, 5, 7, 12, 30)) as clock: clock.forward(days=2) clock.backward(minutes=15) You can use the timedelta arguments`(https://docs.python.org/2/library/datetime.html#datetime.timedelta)_ for “forward` and backward. Static UUIDS When asserting UUIDs it can be annoying that they change at each test run. The staticuid decorator helps to fix that by using static uuids which are prefixed and counted within a scope, usually a test case: from ftw.testing import staticuid from plone.app.testing import PLONE_INTEGRATION_TESTING from unittest2 import TestCase class MyTest(TestCase): layer = PLONE_INTEGRATION_TESTING @staticuid() def test_all_the_things(self): doc = self.portal.get(self.portal.invokeFactory('Document', 'the-document')) self.assertEquals('testallthethings0000000000000001', IUUID(doc)) @staticuid('MyUIDS') def test_a_prefix_can_be_set(self): doc = self.portal.get(self.portal.invokeFactory('Document', 'the-document')) self.assertEquals('MyUIDS00000000000000000000000001', IUUID(doc)) Generic Setup uninstall test ftw.testing provides a test superclass for testing uninstall profiles. The test makes a Generic Setup snapshot before installing the package, then installs and uninstalls the package, creates another snapshot and diffs it. The package is installed without installing its dependencies, because it should not include uninstalling dependencies in the uninstall profile. Appropriate testing layer setup is included and the test runs on a seperate layer which should not interfere with other tests. Simple example: from ftw.testing.genericsetup import GenericSetupUninstallMixin from ftw.testing.genericsetup import apply_generic_setup_layer from unittest2 import TestCase @apply_generic_setup_layer class TestGenericSetupUninstall(TestCase, GenericSetupUninstallMixin): package = 'my.package' The my.package is expected to have a Generic Setup profile profile-my.package:default for installing the package and a profile-my.package:uninstall for uninstalling the package. It is expected to use z3c.autoinclude entry points for loading its ZCML. The options are configured as class variables: package The dotted name of the package as string, which is used for things such as guessing the Generic Setup profile names. This is mandatory. autoinclude (True) This makes the testing fixture load ZCML using the z3c.autoinclude entry points registered for the target plone. additional_zcml_packages (()) Use this if needed ZCML is not loaded using the autoinclude option, e.g. when you need to load testing zcml. Pass in an iterable of dottednames of packages, which contain a configure.zcml. additional_products (()) A list of additional Zope products to install. install_profile_name (default) The Generic Setup install profile name postfix. skip_files (()) An iterable of Generic Setup files (e.g. ("viewlets.xml",)) to be ignored in the diff. This is sometimes necessary, because not all components can and should be uninstalled properly. For example viewlet orders cannot be removed using Generic Setup - but this is not a problem they do no longer take effect when the viewlets / viewlet managers are no longer registered. Full example: from ftw.testing.genericsetup import GenericSetupUninstallMixin from ftw.testing.genericsetup import apply_generic_setup_layer from unittest2 import TestCase @apply_generic_setup_layer class TestGenericSetupUninstall(TestCase, GenericSetupUninstallMixin): package = 'my.package' autoinclude = False additional_zcml_packages = ('my.package', 'my.package.tests') additional_products = ('another.package', ) install_profile_name = 'default' skip_files = ('viewlets.xml', 'rolemap.xml') Disabling quickinstaller snapshots Quickinstaller normally makes a complete Generic Setup (GS) snapshot before and after installing each GS profile, in order to be able to uninstall the profile afterwards. In tests we usually don’t need this feature and want to disable it to speed up tests. The ftw.testing.quickinstaller module provides a patcher for replacing the quickinstaller event handlers to skip creating snapshots. Usually we want to do this early (when loading testing.py), so that all the tests are speeding up. However, some tests which involve quickinstaller rely on having the snapshots made (see previous section about uninstall tests). Therefore the snapshot patcher object provides context managers for temporarily enabling / disabling the snapshot feature. Usage: Disable snapshots early, so that everything is fast. Usually this is done in the testing.py in module scope, so that it happens already when the testrunner imports the tests: from ftw.testing.quickinstaller import snapshots from plone.app.testing import PloneSandboxLayer snapshots.disable() class MyPackageLayer(PloneSandboxLayer): ... When testing quickinstaller snapshot related things, such as uninstalling, the snapshots can be re-enabled for a context manager or in general: from ftw.testing.quickinstaller import snapshots snapshots.disable() with snapshots.enabled(): snapshots.enable() with snapshots.disabled(): Testing Layers Component registry isolation layer plone.app.testing’s default testing layers (such as PLONE_FIXTURE) do not isolate the component registry for each test. ftw.testing’s COMPONENT_REGISTRY_ISOLATION testing layer isolates the component registry for each test, provides a stacked ZCML configuration context and provides the methods load_zcml_string and load_zcml_file for loading ZCML. Example: from ftw.testing.layer import COMPONENT_REGISTRY_ISOLATION from plone.app.testing import IntegrationTesting from plone.app.testing import PloneSandboxLayer from zope.configuration import xmlconfig class MyPackageLayer(PloneSandboxLayer): defaultBases = (COMPONENT_REGISTRY_ISOLATION,) def setUpZope(self, app, configurationContext): import my.package xmlconfig.file('configure.zcml', ftw.package, context=configurationContext) MY_PACKAGE_FIXTURE = MyPackageLayer() MY_PACKAGE_INTEGRATION = IntegrationTesting( bases=(MY_PACKAGE_FIXTURE, COMPONENT_REGISTRY_ISOLATION), name='my.package:integration') from unittest2 import TestCase class TestSomething(TestCase): layer = MY_PACKAGE_INTEGRATION def test(self): self.layer['load_zcml_string']('<configure>...</configure>') Temp directory layer The TEMP_DIRECTORY testing layer creates an empty temp directory for each test and removes it recursively on tear down. The path to the directory can be accessed with the temp_directory key. Usage example: from unittest2 import TestCase from ftw.testing.layer import TEMP_DIRECTORY class TestSomething(TestCase): layer = TEMP_DIRECTORY def test(self): path = self.layer['temp_directory'] Console script testing layer The console script layer helps testing console scripts. On layer setup it creates and executes an isolated buildout with the package under development, which creates all console scripts of this package. This makes it easy to test console scripts by really executing them. Usage example: from ftw.testing.layer import ConsoleScriptLayer CONSOLE_SCRIPT_TESTING = ConsoleScriptLayer('my.package') from my.package.testing import CONSOLE_SCRIPT_TESTING from unittest2 import TestCase class TestConsoleScripts(TestCase): layer = CONSOLE_SCRIPT_TESTING def test_executing_command(self): exitcode, output = self.layer['execute_script']('my-command args') self.assertEqual('something\n', output) Be aware that the dependency zc.recipe.egg is required for building the console scripts. You may put the dependency into your tests extras require.
The goal of plone.app.widgets is to provide an implementation for a new set of javascript widgets being developed in the Plone Mockup project. It overrides existing widgets used in dexterity and archetypes to provide tested and modularized widgets based on the concept of patterns. The widgets that are provided currently are: Adjust Text Size -- Easily change text size on a page. Cookie Directive -- A pattern that checks cookies enabled and asks permission for the user to allow cookies or not. Expose -- Exposes the focused element by darkening everything else on the page. Useful to focus the user attention on a particular area. Form Unload Alert -- A pattern to warn user when changes are unsaved and they try to navigate away from page. Live Search -- Dynamically query the server and display results. Modal -- Creates a modal dialog (also called overlay). Pick A Date -- Allows the user to select a date (with or without time) through a calendar. Picture -- A responsive image widget. Prevent Double Submit -- A pattern to prevent submitting a form twice. Query String for Collections -- A widget for creating query's for collections Related Items -- An advanced widget for selecting related items. Select2 -- Autocompletes, multiple or single selections from any kind of data source (with search!). Table Sorter -- A pattern you can apply to a table so it can have its items rearranged when clicking the header. TinyMCE (v4!!!) -- Rich text editor. Table of Contents -- Automatically generate a table of contents. Tooltip -- A pattern to show a tooltip on hover. DropZone -- Drag 'n drop file upload Widgets that are overridden in Edit forms are: subject language effectiveDate expirationDate contributrors creators relatedItems query All client side code (javascript/css/images) is done and tested as part of Plone Mockup project.
This package provides Audio and Video Dexterity content types and behaviors, conversions and players/views. It integrates the HTML5 media player mediaelementjs and uses plone.app.async if installed to convert videos to common formats. Features Audio and Video types Integration with mediaelementjs designed for maximum forward and backwards compatibility Automatically convert video types to HTML5 compatible video formats Be able to add video from TinyMCE by adding a link to the audio or video objects and then adding one of the available Audio and Video TinyMCE styles. Integration with plone.app.async for conversions if installed Plone 4.3 syndication support Transcript data Youtube URL (in case you want the video streamed from Youtube) Streaming support Still screen shot Subtitle (captioning) file in SRT format
This package provides a tinymce plugin for easily adding youtube videos. Warning ^^^^^^^ You'll likely need to allow iframe as a custom tag via html filtering for this plugin to work. Another warning ^^^^^^^^^^^^^^^ Due to the way most modern browsers help protect against XSS attacks, after you save a form where you added a video with tinymce, you'll likely see the rendering of the video is blocked initially. Just reload the browser once more to see the video added. See http://glicksoftware.com/blog/disable-html-filtering for tips on getting around this annoyance.
Adds support for TinyMCE, a platform independent web based Javascript HTML WYSIWYG editor, to Plone.