{ "info": { "author": "Davide Moro", "author_email": "davide.moro@gmail.com", "bugtrack_url": null, "classifiers": [ "Framework :: Pytest", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Testing" ], "description": "pypom_form\n**********\n\n.. image:: https://travis-ci.org/tierratelematics/pypom_form.svg?branch=develop\n :target: https://travis-ci.org/tierratelematics/pypom_form\n\n.. image:: https://requires.io/github/tierratelematics/pypom_form/requirements.svg?branch=develop\n :target: https://requires.io/github/tierratelematics/pypom_form/requirements/?branch=develop\n\n.. image:: https://readthedocs.org/projects/pypom_form/badge/?version=latest\n :target: http://pypom_form.readthedocs.io\n\n.. image:: https://codecov.io/gh/tierratelematics/pypom_form/branch/develop/graph/badge.svg\n :target: https://codecov.io/gh/tierratelematics/pypom_form\n\n.. image:: https://api.codacy.com/project/badge/Grade/0698c7aa2e164ee996518737aad7d6f4\n :target: https://www.codacy.com/app/davide-moro/pypom_form?utm_source=github.com&utm_medium=referral&utm_content=tierratelematics/pypom_form&utm_campaign=Badge_Grade\n \n.. image:: https://pyup.io/repos/github/tierratelematics/pypom_form/python-3-shield.svg\n :target: https://pyup.io/repos/github/tierratelematics/pypom_form/\n :alt: Python 3\n\n\n``pypom_form`` is a PyPOM based package that provides declarative schema based form interaction for page objects.\n\npypom_form aims to improve the developer experience for UI, E2E test automation when you\nhave to interact with page object containing forms thanks to declarative schema models.\n\nIf you come from past experience with frameworks like `SQLAlchemy`_, Dexterity (`Plone`_) or the old Archetypes (`Plone`_)\nyou should be already familiar with this pattern: you simply define a model with a schema and you will be able to\ninteract with your model saving or retrieving data.\nSame happens with pypom_form where the model is the page.\n\npypom_form it is internally based on:\n\n* `PyPOM`_\n* `colander`_\n* `Splinter`_\n\nHow does it work?\n=================\n\nWhith pypom_form you have just to:\n\n* instanciate a page object instance whose class inherits from BaseFormPage provided by pypom_form\n* declare the schema model\n\nAnd you will be ready for interacting with your page driving the browser with your form just typing::\n\n page.title = 'the title'\n page.title\n\nassuming that you have a ``title`` field in your form.\n\nMain concepts\n-------------\n\nYou might think about the ``schema`` concept as a set of named attributes (``fields``) that will be\navailable on the ``model`` as regular properties.\n\nEach ``field`` on the schema is defined with a ``type`` (eg: string, int, float, datetime, date, bool, etc)\nthat defines the data type for the given field on the application domain level.\n\nFields has a reference to a ``widget`` defined imperatively or assigned by default depending on the field\ntype.\nThe inner implementation of widgets provided by pypom_form is based on PyPOM's Regions, so ``widget regions``\nwraps and manage a DOM containing the widget.\n\nBasically the widget translates data from the applicative domain to the browser domain and vice versa\nthrough serialization and deserialization.\n\nYou might thing about a widget as how you have to driver your browser when you set ``True`` to a boolean\nproperty or get the actual value on the form: basically it depends on the widget implementation. For example\nyou might have a checkbox, yes/no radio buttons or combo select, etc and if you want to set ``True`` the\nway you drive the browser changes. Same for date widgets and so on.\n\nYou might have to deal with complex widgets too like:\n\n* reference widgets (eg: hierarchical content navigation with search, filtering, etc)\n* advanced multi selection widgets\n* dictionary widgets (key value mapping)\n* etc\n\nFor example, assuming you are dealing with a pretend advanced single selection choice field you can\naccess to advanced logics provided by the ``widget region``::\n\n page.getWidgetRegion('state').filter('virg').select('Virginia')\n\nor access to validation error messages, label text, etc.\n\nWhy pypom_form\n--------------\n\nObviously you can drive your browser in automated tests with plain selenium/splinter or with a traditional\nplain page object model pattern but with pypom_form you have the following advantages:\n\n* write once and reusable approach, very useful if you are testing CMS framework\n* separation of concerns for page and widget logics\n* declarative schema approach\n* reusable schema and widgets, no code repetition\n* widgets can be shared with other projects using pypom_form\n* simple API based on auto generated getter and setters\n* interact with advanced widget logics thanks to PyPOM based region widgets\n* widget isolation. All element queries run against the root region, not the page root\n* simpler input elements selectors, they are relative to the region widget root\n* schema forms improves how you document page containing forms (attributes names, type, widgets,\n allowed vocabularies, etc). All you need to know is defined at schema level with the whole picture\n available at a glance\n* reuse of existing schemas if you are going to test a colander/deform based application (probably\n you are testing a Pylons `Pyramid`_ Python based web application)\n* page and schema inheritance supported as well\n* easy test multi skin web applications with same data model, same or different selectors or widget\n types. So you can reuse all your page object classes as they are defined, it changes only the schema\n widget selector adn widget types\n* widget regions are PyPOM regions, so if you want to access inner elements inside the widget container\n the resulting selectors will be simpler because they are relative to the widget region root.\n Also sub/nested regions or dynamic regions are supperted as well\n* interact with your model with applicative domain data instead of browser domain data. It is more\n simple and easy to manage Python data (for example you set 12.9 instead of '12.9', same for datetimes\n values like ``datetime.now()``)\n* supports chained calls like ``page.set('title', 'the title')``\n* supports bulk field updates considering the order defined at schema level via ``page.update(**values)``\n* don't reinvent the wheel. It is based on existing and widely used components like the plain PyPOM or\n Colander libraries\n* same user experience if you are already familiar with schema declarative models like ``SQLAlchemy``,\n ``Archetypes`` (Plone), ``Dexterity`` (Plone) or form libraries like ``deform``\n* since widget implementation is based on regions, you can simply perform a ``page.name = \"the name\"``\n on page load instead of having to call a wait method before setting the value:\n the widget is able to wait for the widget load before getting or setting data\n* page objects classes more simple, with less code, more standard even if different test engineers will\n implement page form logics: there is a structural pattern\n\nIn addition:\n\n* 100% test coverage\n* both Python 2 and 3 support\n* supports Splinter drivers (Selenium support not yet available)\n* pytest setup ready thanks to ``pytest-splinter``\n\nCode samples\n============\n\nThe following code samples assumes that there is a navigation fixture providing the page instance\nbuilt with a Splinter driver but you can build by yourself a page instance following\nthe PyPOM documentation:\n\n* http://pypom.readthedocs.io/en/latest/\n\nSchema definition::\n\n import colander\n \n from pypom_form.form import BaseFormPage\n \n \n class BaseEditSchema(colander.MappingSchema):\n \"\"\" This is the base edit mapping common for all pages \"\"\"\n \n name = colander.SchemaNode(\n colander.String(),\n selector=('id', 'name-widget'),\n )\n \n \n class BaseEditPage(BaseFormPage):\n \"\"\" This is the base edit class \"\"\"\n \n schema_factory = BaseEditSchema\n\nAnd assuming you have a page instance you can interact with the above page\njust setting an attribute::\n\n @pytest_bdd.when(pytest_bdd.parsers.parse(\n 'I set {name} as name field'))\n def fill_name(navigation, name):\n page = navigation.page\n page.name = name\n\nYou can also define other pages with extended schema, for example an integer\ntype::\n\n class AnotherPageEditSchema(BaseEditSchema):\n \n duration = colander.SchemaNode(\n colander.Int(),\n missing=0,\n selector=('id',\n 'duration-widget'),\n validator=colander.Range(0, 9999))\n\nbut you can create also field types like ``colander.Bool`` or any other colander\nsupported types.\n\nAnd the test::\n\n @pytest_bdd.when(pytest_bdd.parsers.cfparse(\n 'I set {duration:Number} as Alarm duration',\n extra_types=dict(Number=int)))\n def fill_alarm_duration(navigation, duration):\n page = navigation.page\n page.duration = duration\n\nYou might notice that in the above example you are setting an integer duration\nand not a string. So you can perform ``page.duration += 10`` for example. \n\nYou can also define custom widgets on fields if the default implementation does\nnot match the one available on your application (for example a non standard\ncheckbox for a boolean widget), for example a pretend ``MyBooleanWidget``::\n\n mybool = colander.SchemaNode(\n colander.Bool(),\n missing=False,\n selector=(\n 'id',\n 'mybool-widget'\n ),\n pypom_widget=MyBoolWidget()\n )\n\nAlso chained calls are supported (eg: set the title, perform the pretend submit method\nand then set a boolean)::\n\n page.set('title', 'the title'). \\\n .submit(). \\\n .set('mybool', False)\n\nor bulk updates. All changes occurs following the fields order at schema level::\n\n page.update(**{'title': 'the title', 'mybool': True})\n\nThe ``update`` or ``raw_update`` can be used in test preconditions creation.\nAssuming you have a generic given step with parametrized with a complex configuration\nyou can pass the raw json data and the ``raw_update`` will take care about the\ndata conversion from browser model (eg: string) to the page model (strings, integers,\ndatetimes, etc)::\n\n @pytest_bdd.given(pytest_bdd.parsers.cfparse(\n 'I have a CAN bus protocol configured with:\\n{raw_conf:json}',\n extra_types=dict(json=json.loads)))\n def create_can_protocol(navigation, base_url, raw_conf):\n \"\"\" create a can protocol\n \"\"\"\n\n navigation. \\\n visit_page('CANBusProtocolsPage'). \\\n wait_for_full_spinner(). \\\n click_add(). \\\n raw_update(**raw_conf). \\\n save(). \\\n wait_for_success_pop_up_appears(). \\\n click_on_ok_pop_up()\n\nassuming that the ``raw_conf`` is specified in json format in\nthe ``.feature`` file, for example::\n\n @UI @edit @CANBusParameter\n Scenario: Add a CAN bus parameter\n Given I am logged in as Administrator\n And I have a CAN bus protocol configured with:\n {\"name\": \"The name\",\n \"baudrate\": \"250\",\n ...\n }\n And ...\n\nAs you can see in the above code examples there is no need to perform wait calls before\ninteracting with a form on page load because each widget is able to wait until its\ncontrolled input element is ready. Wait logics are already defined on widget level and\nyou can override them.\n\n\n.. _PyPOM: http://pypom.readthedocs.io\n.. _colander: http://docs.pylonsproject.org/projects/colander/en/latest/\n.. _Splinter: https://splinter.readthedocs.io/en/latest/\n.. _Plone: https://plone.org/\n.. _SQLAlchemy: http://www.sqlalchemy.org/\n.. _Pyramid: https://trypyramid.com/\n\nChangelog\n*********\n\n0.3.1 (2017-10-24)\n==================\n\n- Added a ``raw_dump`` method to get all the fields serialized.\n\n\n0.3.0 (2017-10-10)\n==================\n\n- Added a ``dump`` method to get all the fields in bulk.\n\n\n0.2.3 (2017-09-14)\n==================\n\n- Now you can override automatically generated methods and\n invoke ``super`` inside them\n\n\n0.2.2 (2017-07-03)\n==================\n\n- Moved ``test_fields.py`` to right location.\n\n- Don't override ``update`` and ``raw_update`` methods on pages and\n regions if already exist.\n\n\n0.2.1 (2017-06-01)\n==================\n\n- Update email project \n\n\n0.2.0 (2017-01-24)\n==================\n\nFeatures\n--------\n\n- Added ``serialize`` and ``deserialize`` methods on widgets base\n implementation for advanced usage.\n This way you can implement complex/composed widgets more easily\n just overriding the above methods (eg: perform an intermediate\n conversion from page model data to browser or widget internal\n representation data)\n\n- Added an ``ObjectType`` field for advanced usage when you\n have to implement complex or composed widgets\n\n- Added support for ``readonly`` fields. If a field is marked as\n read only, no browser interaction will be performed\n\n\n0.1.0 (2017-01-03)\n==================\n\nFeatures\n--------\n\n- Added widget reference to the widget region so you can\n navigate to the widget from the widget region and access\n to widget options specified on the schema\n\n- Added TextAreaWidget\n\nDocumentation\n-------------\n\n- Improved documentation\n\n\n0.0.1 (2016-12-22)\n==================\n\n- Initial release", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://pypom-form.readthedocs.io/en/latest/", "keywords": "", "license": "Apache License, Version 2.0", "maintainer": "", "maintainer_email": "", "name": "pypom-form", "package_url": "https://pypi.org/project/pypom-form/", "platform": "", "project_url": "https://pypi.org/project/pypom-form/", "project_urls": { "Homepage": "http://pypom-form.readthedocs.io/en/latest/" }, "release_url": "https://pypi.org/project/pypom-form/0.3.1/", "requires_dist": null, "requires_python": "", "summary": "pypom_form", "version": "0.3.1" }, "last_serial": 3274438, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "bc52ef67cdf9f5507c38325d3e85282c", "sha256": "f764a9f53558985ad1c86b172c7b332a27d196a8fa5ee5cf890a075d8c9d34bc" }, "downloads": -1, "filename": "pypom_form-0.0.1-py2-none-any.whl", "has_sig": false, "md5_digest": "bc52ef67cdf9f5507c38325d3e85282c", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 18187, "upload_time": "2016-12-22T17:23:09", "url": "https://files.pythonhosted.org/packages/38/c3/e63ede72ad9e6383bf967b9b7efb14ba2a0adce342be7e08190f011ed8f9/pypom_form-0.0.1-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "081e1d8e1cf5b8a3a3bf48eeafe0ea0c", "sha256": "d07f27e2193c144bd88ea5b75bc25f5fc78d59ebe11653d98593304c4a751b07" }, "downloads": -1, "filename": "pypom_form-0.0.1.tar.gz", "has_sig": false, "md5_digest": "081e1d8e1cf5b8a3a3bf48eeafe0ea0c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46223, "upload_time": "2016-12-22T17:23:11", "url": "https://files.pythonhosted.org/packages/24/00/26325fc54723adb3b2825e5ae0d2d7fabda794ebad4a752e621059bbc395/pypom_form-0.0.1.tar.gz" } ], "0.1.0": [ { "comment_text": "", "digests": { "md5": "977fc8cd5c18513f2ea75c89b7afb5a8", "sha256": "d6480ef9683b5fa159de60561d811bfdd06bc91e7430ee70c20e69828a629c44" }, "downloads": -1, "filename": "pypom_form-0.1.0-py2-none-any.whl", "has_sig": false, "md5_digest": "977fc8cd5c18513f2ea75c89b7afb5a8", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 18477, "upload_time": "2017-01-03T15:48:44", "url": "https://files.pythonhosted.org/packages/e0/52/07506264b45f57f534c7f24e4c1032a08759723e3e62e6d8da6b844b81a5/pypom_form-0.1.0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "35d3478dae5395422cff8e79413fae1f", "sha256": "599174a290f9c6057f7e0d1436d6bdc43cb090771892254ce6f4d6362d272074" }, "downloads": -1, "filename": "pypom_form-0.1.0.tar.gz", "has_sig": false, "md5_digest": "35d3478dae5395422cff8e79413fae1f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 67116, "upload_time": "2017-01-03T15:41:28", "url": "https://files.pythonhosted.org/packages/3e/b6/adda6b2ad81454c38a390f64b7fd744bc5ec3f89bfbfcc6618ca02a11412/pypom_form-0.1.0.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "e6890da6daf67a26fa17b29b04957519", "sha256": "498035b65e4ffc45634c6dd7e76885606cb52d0ead327c9f0b45da4b374f6624" }, "downloads": -1, "filename": "pypom_form-0.2.0.tar.gz", "has_sig": false, "md5_digest": "e6890da6daf67a26fa17b29b04957519", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 68169, "upload_time": "2017-01-24T17:12:42", "url": "https://files.pythonhosted.org/packages/fb/da/f50ba127f40514c13d40a4f552b1e018e7e94badc4c3a4ebada61cdf292d/pypom_form-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "bb7ba1bb717a161027ef660641de199a", "sha256": "ae8420f5bb7dc92d45263d85f5181dadadf98e1200b46cc87f7af35aa8bbe968" }, "downloads": -1, "filename": "pypom_form-0.2.1.tar.gz", "has_sig": false, "md5_digest": "bb7ba1bb717a161027ef660641de199a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 69242, "upload_time": "2017-06-01T09:06:12", "url": "https://files.pythonhosted.org/packages/38/24/6aa00f3469fd6e7f0a183ac74787b621df5b4c363dbdc259f05252aa3a8a/pypom_form-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "e7372272cdbf09be1a6437740226b44f", "sha256": "05fdd6143af000cc9bb5cba7ff4765b3a71e916098a19239cf4b42ca2f123242" }, "downloads": -1, "filename": "pypom_form-0.2.2.tar.gz", "has_sig": false, "md5_digest": "e7372272cdbf09be1a6437740226b44f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 69529, "upload_time": "2017-07-03T14:41:21", "url": "https://files.pythonhosted.org/packages/7f/66/716ae57658618d207297c5d38f304b4b9b8c26e3e74482fa5c629473278c/pypom_form-0.2.2.tar.gz" } ], "0.2.3": [ { "comment_text": "", "digests": { "md5": "7349f6ec1d51de11d64181707788e59a", "sha256": "c3d36688ed2d78c4c2001269cd2b2587bc3f1a317636c7a22bc799955f22e6da" }, "downloads": -1, "filename": "pypom_form-0.2.3.tar.gz", "has_sig": false, "md5_digest": "7349f6ec1d51de11d64181707788e59a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 49582, "upload_time": "2017-09-14T12:30:42", "url": "https://files.pythonhosted.org/packages/df/b3/0d36f760071eb3e4c266ff1855093bca982c7cdf26c7e08808f67352fd49/pypom_form-0.2.3.tar.gz" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "431a103d4fe6c82f4810bd0050e198c9", "sha256": "a342be289501b3f3b129f8d0bbb0dd198bf91c0e43f1743600200e9a5cc54bb2" }, "downloads": -1, "filename": "pypom_form-0.3.0.tar.gz", "has_sig": false, "md5_digest": "431a103d4fe6c82f4810bd0050e198c9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 49863, "upload_time": "2017-10-10T12:53:26", "url": "https://files.pythonhosted.org/packages/cb/d3/3a1151b0cf7ea54752d3b6f0aa703b398a666d10ae9885165725fd9fb6b8/pypom_form-0.3.0.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "2dc60699d52964668f8e732596f6f13e", "sha256": "0b65ab4c5250c79be237600d4ed5b9ca5ea025619bc0320eed9602c4f17f81d2" }, "downloads": -1, "filename": "pypom_form-0.3.1.tar.gz", "has_sig": false, "md5_digest": "2dc60699d52964668f8e732596f6f13e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 49982, "upload_time": "2017-10-24T08:21:34", "url": "https://files.pythonhosted.org/packages/a3/ab/10ef782713f272d9d4da6fe79cebb123e54d27f0062c2d7d645b8cb539a1/pypom_form-0.3.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "2dc60699d52964668f8e732596f6f13e", "sha256": "0b65ab4c5250c79be237600d4ed5b9ca5ea025619bc0320eed9602c4f17f81d2" }, "downloads": -1, "filename": "pypom_form-0.3.1.tar.gz", "has_sig": false, "md5_digest": "2dc60699d52964668f8e732596f6f13e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 49982, "upload_time": "2017-10-24T08:21:34", "url": "https://files.pythonhosted.org/packages/a3/ab/10ef782713f272d9d4da6fe79cebb123e54d27f0062c2d7d645b8cb539a1/pypom_form-0.3.1.tar.gz" } ] }