Progress report and next steps

Introduction

The project is still young but the progress made so far is encouraging and we’re still missing a nice user interface but we already have a test runner which can run the same suites on Orbeon Forms, XSLTForms and betterFORM (the test runner itself is implemented in Orbeon Forms but is able to delegate tests to XSLTForms and betterFORM).

I can’t show anything very spectacular yet but with important building blocks already available, I am now confident that what I had in mind can be implemented and have a much better idea of what can be achieved.

 The product

The idea is to develop a web based environment with a user friendly user interface to create, update and run XForms test suites.

This environment itself will be based on Orbeon Forms (other implementations could be developed depending on the demand) but the tests will be run on different XForms implementations (Orbeon Forms, XSLTForms and betterFORM will be the first three candidates, other implementations can be added).

An installer will be available so that the environment can easily be installed on your platform. An on-line version could be provided too, but there are severe security implications to run arbitrary XForms forms on a server and this would require serious investigations.

Benefits

The tool should be useful for testing XForms forms against a single XForms implementation but also to check the interoperability of forms between different implementations.

This project is also a unique opportunity to detect interoperability issues between implementations and I hope that it will improve the communication between the different communities (I have the feeling that today there is no XForms developers community but three isolated Orbeon Forms, XSLTForms and betterFORM developers communities).

Finally, even if the end goal is different, I hope that this work can be beneficial for the next version of W3C XForms test suites.

 Next steps

XForms Unit 1.0 will include fully functional versions of the installer and test runner. The test runner will be able to run test suites and display test results but the edition of the test suites will have to be done “manually”, using a tool such as oXygen or eXide.

XForms Unit 1.1 will add an user friendly interface to edit the suites.

 Funding and contributions

I have started this project in the hope that I will be able to use it on my XForms projects and also to get an opportunity to work with XSLTForms and betterFORM (I am an Orbeon Forms expert but I haven’t done much with other XForms implementations yet).

The project is already exceeding my expectations in these domains but it’s also taking a lot of my time and at some point I will probably need to find a way to fund it. I don’t know yet if it will might be through donations, sponsoring, support or deriving a commercial version but I’ll definitely need some funding. If you want to sponsor a feature, please contact me.

You can contribute to the project by spreading the word, adding comments to the articles on this site, editing the wiki and ticket system (to do so you need to log in through the OpenID login)…

Progress report

If you’ve read that far and are ready for lots of boring angle brackets and HTTP request, this section is for you!

Principles

In practice, if you have a form such as:

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms">
    <head>
        <title>Hello World in XForms</title>
        <xf:model id="model">
            <xf:instance id="instance" xmlns="">
                <data>
                    <PersonGivenName></PersonGivenName>
                    <Greetings></Greetings>
                </data>
            </xf:instance>
            <xf:bind id="greetings" nodeset="/data/Greetings"
                calculate="concat('Hello ', ../PersonGivenName, '. We hope you like XForms!')"/>
        </xf:model>
    </head>
    <body>
        <p>Type your first name in the input box. <br /> If you are running XForms, the output should be displayed in
            the output area.</p>
        <xf:input ref="PersonGivenName" incremental="true">
            <xf:label>Please enter your first name: </xf:label>
        </xf:input>
        <br />
        <xf:output value="Greetings">
            <xf:label>Output: </xf:label>
        </xf:output>
    </body>
</html>

hello-world.xhtml

You can write the following test suite to test that the calculated value for the greetings element is correct:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="../../suite.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="../../suite.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<suite xmlns:xh="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <form src="hello-world.xhtml"/>

    <!-- The test cases -->
    <case id="test-greetings">
        <title>Test that greetings are correctly set</title>
        <controller.setvalue ref="instance('instance')/PersonGivenName">Eric</controller.setvalue>
        <model.assertEqual>
            <actual ref="instance('instance')/Greetings"/>
            <expected>Hello Eric. We hope you like XForms!</expected>
            <message>The greetings should be the concatenation of "Hello ", the given name and ". We hope you like
                XForms!".</message>
        </model.assertEqual>
    </case>
</suite>

suite.xml

The test runner transforms the form to add actions that check the tests defined in the suite and you can run these tests in a number of different ways on all three XForms implementations.

The tests can be run and displayed in the resulting form itself. A section is then added to the form to display the results before the resulting form. For this example, the resulting form would be:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xh="http://www.w3.org/1999/xhtml" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xfu="http://xformsunit.org/">
    <head>
        <title>Hello World in XForms</title>
        <xf:model id="model">
            <xf:instance id="instance">
                <data xmlns="">
                    <PersonGivenName/>
                    <Greetings/>
                </data>
            </xf:instance>
            <xf:bind id="greetings" nodeset="/data/Greetings" calculate="concat('Hello ', ../PersonGivenName, '. We hope you like XForms!')"/>
            <!-- Test cases -->
            <xf:instance id="xfu-instance">
                <suite xmlns="">
                    <case id="test-greetings">
                        <title id="d28e12">Test that greetings are correctly set</title>
                        <controller.setvalue ref="instance('instance')/PersonGivenName" id="d28e15">Eric</controller.setvalue>
                        <model.assertEqual id="d28e18" passed="">
                            <actual ref="instance('instance')/Greetings" id="d28e20"/>
                            <expected id="d28e22">Hello Eric. We hope you like XForms!</expected>
                            <message id="d28e25">The greetings should be the concatenation of "Hello ", the given name and ". We hope you like XForms!".</message>
                        </model.assertEqual>
                    </case>
                </suite>
            </xf:instance>
            <xf:dispatch ev:event="xforms-ready" targetid="model" name="xfu-d28e15-action"/>
            <xf:action ev:event="xfu-d28e15-action">
                <xf:recalculate/>
                <xf:refresh/>
                <xf:setvalue ref="instance('instance')/PersonGivenName">Eric</xf:setvalue>
                <xf:dispatch targetid="model" name="xfu-d28e18-action"/>
            </xf:action>
            <xf:action ev:event="xfu-d28e18-action">
                <xf:recalculate/>
                <xf:refresh/>
                <xf:setvalue ref="instance('xfu-instance')//*[@id = 'd28e20']" value="instance('instance')/Greetings"/>
                <xf:setvalue ref="instance('xfu-instance')//*[@id = 'd28e18']/@passed" value="(instance('instance')/Greetings) = 'Hello Eric. We hope you like XForms!'"/>
            </xf:action>
        </xf:model>
    </head>
    <body>
        <xf:group ref="instance('xfu-instance')" id="xfu-group">
            <h3>Test results</h3>
            <dl>
                <xf:repeat nodeset="case">
                    <dt>
                        <dfn>
                            <xf:output ref="@id"/>
                        </dfn>
                    </dt>
                    <dd>
                        <ul>
                            <xf:repeat nodeset="*[@passed]">
                                <li>
                                    <xf:group ref=".[@passed = 'true']">
                                        <span>passed</span>
                                    </xf:group>
                                    <xf:group ref=".[@passed = 'false']">
                                        <span>failed</span>
                                        <xf:output ref="*:actual|control">
                                            <xf:label>Actual :</xf:label>
                                        </xf:output>
                                        <xf:output ref="*:expected">
                                            <xf:label>Expected :</xf:label>
                                        </xf:output>
                                    </xf:group>
                                </li>
                            </xf:repeat>
                        </ul>
                    </dd>
                </xf:repeat>
            </dl>
        </xf:group>
        <hr/>
        <h3>Form</h3>
        <p>Type your first name in the input box.
            <br/>If you are running XForms, the output should be displayed in the output area.
        </p>
        <xf:input ref="PersonGivenName" incremental="true">
            <xf:label>Please enter your first name:</xf:label>
        </xf:input>
        <br/>
        <xf:output value="Greetings">
            <xf:label>Output:</xf:label>
        </xf:output>
    </body>
</html>

Running the tests

Tests run in Orbeon Forms (note how  the XForms instance inspector can be used to see the test results):

Tests run on Orbeon Forms

Tests run in XSLTForms (the Profiler could be used to display the test results):

Tests run on XSLTForms

Tests run in betterFORM:

Tests on betterFORM

These tests are executed through the same URL (http://localhost:8080/orbeon/xformsunit/suite/hello-world/tests) with a query parameter that identifies the implementation (?implementation=orbeon|xsltforms|betterform, orbeon being the default) but under the cover these three cases are handled differently:

  • When  implementation=orbeon the form is just sent to Orbeon’s standard page flow epilogue which runs its standard XForms processing.
  • When implementation=xsltforms the xml-stylesheet processing instruction that starts the XSLTForms client side processing is added to the form. To avoid the XForms processing in Orbeon’s page flow epilogue, the form is converted as a text document before being sent to the epilogue. The standard epilogue processing will then convert the form back in XML without applying any XForms processing. The remaining part of the story is then classical XSLTForms processing (a XSLTForms installation directory has been added in Orbeon’s resources and is served as static resources).
  • When implementation=betterform a plain eXist 2.x server is installed side by side with Orbeon and the page is redirected to an eXist service which reads the form from Orbeon and executes it through its standard betterFORM processing.

The difference is obvious when you look at network exchanges.

For Orbeon the tests follow a classical Orbeon Forms XForms processing:

Network exchanges: Orbeon implementation

For XSLTForms the pattern is similar but we see that XSLT transformations are loaded and executed before the first resources (CSS, scripts, images, …) are read:Network exchanges, XSLTForms implementation

For betterForm a first request is sent to Orbeon and we have a redirection to eXist for betterFORM processing (I have no idea why Firebug doesn’t display the resources loaded by betterFORM):

Network exchange: betterFORM implementation

Getting the results back

This feature is useful to run the tests and display their results in a browser, but we may prefer to get the test results in XML. To do so we add an action at the end of the tests post the results to an echo service which returns them. The submission is done with @replace=”all” and the results replace the current page in the browser.

For all three implementations, the results are the description of the test suite in which passed attributes have been added to say if assertions have been found true or not and actual values have been filled in:

<suite>
    <case id="test-greetings">
        <title id="d68e12">Test that greetings are correctly set</title>
        <controller.setvalue ref="instance('instance')/PersonGivenName" id="d68e15">Eric</controller.setvalue>
        <model.assertEqual id="d68e18" passed="true">
            <actual ref="instance('instance')/Greetings" id="d68e20">Hello Eric. We hope you like XForms!</actual>
            <expected id="d68e22">Hello Eric. We hope you like XForms!</expected>
            <message id="d68e25">The greetings should be the concatenation of "Hello ", the given name and ". We hope you like XForms!".</message>
        </model.assertEqual>
    </case>
</suite>

The principle is similar but here again the details of the implementation vary and the network exchanges are quite different. These exchanges are too complex to follow them with Firebug and we’ll move to Wireshark to track them.

The exchange for the Orbeon implementation is as simple as possible:

HTTP traffic for the Orbeon implementation

Sounds good, but why is that so simple and why don’t we see the post to the echo service?

This is because the submission uses the “echo:” pseudo service:

<xf:submission id="send-instance" ref="instance('xfu-instance')" action="echo:" method="post" replace="all"/>

This “echo:” URL scheme is an Orbeon Forms extension which returns the instance itself. The magic here is that because this submission is performed during the xforms-ready handler everything is done server side, without any client/server interaction and without any browser-side processing.

The huge benefit is that we don’t need any browser to run these tests and that we could perform and load the results server side using cURL or wget or even run them using Orbeon Forms in command line mode.

The traffic with XSLTForms is what could be expected: Network traffic with the XSMTForms implementation

After the initial HTTP GET (record 925) and its answer (927), XSLT resources are loaded, followed (after the transformation) by CSS and JS. The tests can then take place and the submission posts the results (1013) which are returned to the browser (1014).

The exchanges for the betterFORM implementation follow the principles which we’ve already seen:

HTTP traffic with the betterFORM implementation

The response to the initial HTTP request (4) is a 302 redirection (6) to the eXist “pull.xq” service. This service implemented by a simple XQuery query pulls the form from Orbeon (16 and 18), performs server side betterFORM processing and sends the resulting page to the browser (20). Resources are loaded, betterFORM client/server exchanges are performed (287 and 299) and the tests results are posted to the echo service (305 and 307). The results are eventually loaded in the browser through a final HTTP GET exchange (310 and 311).

Note that XForms submission with @replace=”all” raises an error when performed in an xforms-ready event handler with betterFORM. To get all that working I had to use a workaround and trigger an event which in turn does the submission in JavaScript:

        <script type="text/javascript">window.onload = function() {fluxProcessor.dispatchEvent('trigger-send-results');};</script>
    </head>
    <body>
        <xf:group ref="instance('xfu-instance')" id="xfu-group">
            <h3>Test results</h3>
            <p>
                <xf:trigger id="trigger-send-results">
                    <xf:label>Send results</xf:label>
                    <xf:send ev:event="DOMActivate" submission="send-instance"/>
                </xf:trigger>
            </p>

If we pay a closer attention to the traffic exchange for betterFORM, we see that the initialization of the form (which includes xforms-ready event handlers) is triggered by the browser (POST /exist/Flux/call/plaincall/Flux.init.dwr, record 287) which means that (unless we can find some tricky workaround) this initialization cannot be performed server side within eXist/betterFORM and requires an exchange with a browser supporting JavaScript.

Batch processing

We’ve seen so far how we can execute the tests and get their results in a browser. That’s fine unless you need to run these tests server side, without browser interaction.

To illustrate the problem to solve, let’s have a look at what’s happening if we try to fetch these URLs with a tool such as cURL, wget or even lynx.

When the implementation is Orbeon we get the result straight away:

 vdv@ldlc:~$ curl -Lv http://localhost:8080/orbeon/xformsunit/suite/hello-world/results?implementation=orbeon
* About to connect() to localhost port 8080 (#0)
*   Trying 127.0.0.1... connected
> GET /orbeon/xformsunit/suite/hello-world/results?implementation=orbeon HTTP/1.1
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: localhost:8080
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Set-Cookie: JSESSIONID=E3C95DF6438399D21B5F257F20EB2A9A; Path=/orbeon/; HttpOnly
< Last-Modified: Thu, 12 Sep 2013 14:54:11 GMT
< Expires: Thu, 12 Sep 2013 14:54:11 GMT
< Cache-Control: private, max-age=0
< Content-Type: application/xml
< Content-Length: 929
< Date: Thu, 12 Sep 2013 14:54:11 GMT
< 
<?xml version="1.0" encoding="UTF-8"?><suite xmlns:xfu="http://xformsunit.org/" xmlns:xh="http://www.w3.org/1999/xhtml" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xf="http://www.w3.org/2002/xforms"><case id="test-greetings">
        <title id="d116e12">Test that greetings are correctly set</title>
        <controller.setvalue ref="instance('instance')/PersonGivenName" id="d116e15">Eric</controller.setvalue>
        <model.assertEqual id="d116e18" passed="true">
            <actual ref="instance('instance')/Greetings" id="d116e20">Hello Eric. We hope you like XForms!</actual>
            <expected id="d116e22">Hello Eric. We hope you like XForms!</expected>
            <message id="d116e25">The greetings should be the concatenation of "Hello ", the given name and ". We hope you like
                XForms!".</message>
        </model.assertEqual>
* Connection #0 to host localhost left intact
* Closing connection #0
    </case></suite>vdv@ldlc:~$

This isn’t a surprise since we’ve seen that when we use the “echo:” pseudo protocol in a submission with @replace=all in and xforms-ready event handler the processing is entirely done server side. Of course, things will not be that simple for XSLTForms where curl will just get the form straight away:

vdv@ldlc:~$ curl -Lv http://localhost:8080/orbeon/xformsunit/suite/hello-world/results?implementation=xsltforms
* About to connect() to localhost port 8080 (#0)
*   Trying 127.0.0.1... connected
> GET /orbeon/xformsunit/suite/hello-world/results?implementation=xsltforms HTTP/1.1
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: localhost:8080
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Last-Modified: Thu, 12 Sep 2013 14:58:07 GMT
< Expires: Thu, 12 Sep 2013 14:58:07 GMT
< Cache-Control: private, max-age=0
< Content-Type: application/xml;charset=utf-8
< Content-Length: 4387
< Date: Thu, 12 Sep 2013 14:58:07 GMT
< 
<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet href="http://localhost:8080/orbeon/xsltforms/xsltforms.xsl" type="text/xsl"?><?xsltforms-options debug="yes"?><!-- 

    Copyright 2013 Eric van der Vlist <vdv@dyomedea.com>

    This file is part of XForms Unit - http://xformsunit.org.

    XForms Unit is free software: you can redistribute it and/or modify
    it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as 
    published by the Free Software Foundation, either version 3 of the 
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
--><html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:xh="http://www.w3.org/1999/xhtml"
      xmlns:ev="http://www.w3.org/2001/xml-events"
      xmlns:xfu="http://xformsunit.org/">
    <head>
        <title>Hello World in XForms</title>
        <xf:model id="model">
            <xf:instance id="instance">
                <data xmlns="">
                    <PersonGivenName/>
                    <Greetings/>
                </data>
            </xf:instance>
            <xf:bind id="greetings" nodeset="/data/Greetings"
         calculate="concat('Hello ', ../PersonGivenName, '. We hope you like XForms!')"/>
        <!-- Test cases --><xf:instance id="xfu-instance"><suite xmlns=""><case id="test-greetings">
        <title id="d120e12">Test that greetings are correctly set</title>
        <controller.setvalue ref="instance('instance')/PersonGivenName" id="d120e15">Eric</controller.setvalue>
        <model.assertEqual id="d120e18" passed="">
            <actual ref="instance('instance')/Greetings" id="d120e20"/>
            <expected id="d120e22">Hello Eric. We hope you like XForms!</expected>
            <message id="d120e25">The greetings should be the concatenation of "Hello ", the given name and ". We hope you like
                XForms!".</message>
        </model.assertEqual>
    </case></suite></xf:instance><xf:dispatch ev:event="xforms-ready" targetid="model" name="xfu-d120e15-action"/><xf:action ev:event="xfu-d120e15-action"><xf:recalculate/><xf:refresh/><xf:setvalue ref="instance('instance')/PersonGivenName">Eric</xf:setvalue><xf:dispatch targetid="model" name="xfu-d120e18-action"/></xf:action><xf:action ev:event="xfu-d120e18-action"><xf:recalculate/><xf:refresh/><xf:setvalue ref="instance('xfu-instance')//*[@id = 'd120e20']"
             value="instance('instance')/Greetings"/><xf:setvalue ref="instance('xfu-instance')//*[@id = 'd120e18']/@passed"
             value="(instance('instance')/Greetings) = 'Hello Eric. We hope you like XForms!'"/><xf:send submission="send-instance"/></xf:action><xf:submission id="send-instance" ref="instance('xfu-instance')"
               action="http://localhost:8080/orbeon/xformsunit/store-data?uid=suite-fedd90dc-38c6-4208-9e24-aa60b8089ded"
               method="post"
               replace="all"/></xf:model>
    </head>
    <body><xf:group ref="instance('xfu-instance')" id="xfu-group"><h3>Test results</h3><dl><xf:repeat nodeset="case"><dt><dfn><xf:output ref="@id"/></dfn></dt><dd><ul><xf:repeat nodeset="*[@passed]"><li><xf:group ref=".[@passed = 'true']"><span>passed</span></xf:group><xf:group ref=".[@passed = 'false']"><span>failed</span><xf:output ref="*:actual|control"><xf:label>Actual : </xf:label></xf:output><xf:output ref="*:expected"><xf:label>Expected : </xf:label></xf:output></xf:group></li></xf:repeat></ul></dd></xf:repeat></dl></xf:group><hr/><h3>Form</h3>
        <p>Type your first name in the input box. <br/> If you are running XForms, the output should be displayed in
            the output area.</p>
        <xf:input ref="PersonGivenName" incremental="true">
            <xf:label>Please enter your first name: </xf:label>
        </xf:input>
        <br/>
        <xf:output value="Greetings">
            <xf:label>Output: </xf:label>
        </xf:output>
    </body>
* Connection #0 to host localhost left intact
* Closing connection #0
</html>vdv@ldlc:~$

Or for betterFORM where we’ll get the form after a 302 redirect:

vdv@ldlc:~$ curl -Lv http://localhost:8080/orbeon/xformsunit/suite/hello-world/results?implementation=betterform
* About to connect() to localhost port 8080 (#0)
*   Trying 127.0.0.1... connected
> GET /orbeon/xformsunit/suite/hello-world/results?implementation=betterform HTTP/1.1
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: localhost:8080
> Accept: */*
> 
< HTTP/1.1 302 D�plac� Temporairement
< Server: Apache-Coyote/1.1
< Location: http://localhost:8088/exist/apps/betterform/xformsunit/pull.xq?url=http%3A%2F%2Flocalhost%3A8080%2Forbeon%2Fxformsunit%2Fsuite%2Fhello-world%2Fexport-result-form%3Fimplementation%3Dbetterform
< Content-Length: 0
< Date: Thu, 12 Sep 2013 15:01:04 GMT
< 
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8088/exist/apps/betterform/xformsunit/pull.xq?url=http%3A%2F%2Flocalhost%3A8080%2Forbeon%2Fxformsunit%2Fsuite%2Fhello-world%2Fexport-result-form%3Fimplementation%3Dbetterform'
* About to connect() to localhost port 8088 (#1)
*   Trying 127.0.0.1... connected
> GET /exist/apps/betterform/xformsunit/pull.xq?url=http%3A%2F%2Flocalhost%3A8080%2Forbeon%2Fxformsunit%2Fsuite%2Fhello-world%2Fexport-result-form%3Fimplementation%3Dbetterform HTTP/1.1
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: localhost:8088
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Thu, 12 Sep 2013 15:01:04 GMT
< Set-Cookie: JSESSIONID=1u66qziwysv5iaa3ibank0pj5;Path=/exist
< X-XQuery-Cached: true
< Cache-Control: private, no-store,  no-cache, must-revalidate
< Pragma: no-cache
< Expires: -1
< Content-Type: text/html;charset=UTF-8
< Content-Length: 10061
< Server: Jetty(8.1.8.v20121106)
< 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
  SYSTEM "/resources/xsd/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <!-- *** powered by betterFORM, &copy; 2012 *** --><head xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Hello World in XForms</title>
<style type="text/css">
                    @import "/exist/bfResources/scripts/dijit/themes/tundra/tundra.css";

                </style>
<link rel="stylesheet" type="text/css" href="/exist/bfResources/styles/xforms.css" /><link rel="stylesheet" type="text/css" href="/exist/bfResources/styles/betterform.css" />

<script type="text/javascript">
                    window.onload = function() {fluxProcessor.dispatchEvent('trigger-send-results');};
                </script>
</head>
    <body xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><div id="bfLoading"><img id="indicator" src="/exist/bfResources/images/indicator.gif" alt="loading" /></div><div id="betterformMessageToaster"></div><noscript><div id="noScript">
                    Sorry, this page relies on JavaScript which is not enabled in your browser.
                </div></noscript><div id="formWrapper" style="display:none"><form name="betterform" onSubmit="return false;" method="POST" enctype="application/x-www-form-urlencoded" action="javascript:submitFunction();"><input type="hidden" id="bfSessionKey" name="sessionKey" value="1378998064691" /><span xmlns="" xmlns:bfc="http://betterform.sourceforge.net/xforms/controls" id="xfu-group"><span style="display:none;"></span>
            <h3 xmlns="http://www.w3.org/1999/xhtml">Test results</h3>
            <p xmlns="http://www.w3.org/1999/xhtml">
                <span id="trigger-send-results"><input id="trigger-send-results-value" name="t_trigger-send-results" tabindex="" title="" type="button" value="Send results" /></span>
            </p>
            <dl xmlns="http://www.w3.org/1999/xhtml">
                <div xmlns="" id="C14-prototype"><div style="display:none;"></div><dt xmlns="http://www.w3.org/1999/xhtml" xmlns:bf="http://betterform.sourceforge.net/xforms" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:xfu="http://xformsunit.org/" xmlns:xh="http://www.w3.org/1999/xhtml" xmlns:xs="http://www.w3.org/2001/XMLSchema">
                        <dfn>
                            <span xmlns="" id="C16" data-bf-class="xfControl xfOutput aDefault C14-2 repeated mediatypeText"><label></label><span><span xmlns="http://www.w3.org/1999/xhtml" id="C16-value" tabindex="0" title=""></span></span></span>
                        </dfn>
                    </dt><dd xmlns="http://www.w3.org/1999/xhtml" xmlns:bf="http://betterform.sourceforge.net/xforms" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:xfu="http://xformsunit.org/" xmlns:xh="http://www.w3.org/1999/xhtml" xmlns:xs="http://www.w3.org/2001/XMLSchema">
                        <ul>
                            <div xmlns="" id="C17" data-bf-class="xfContainer xfRepeat aDefault"></div>
                        </ul>
                    </dd></div><div xmlns="" id="C17-prototype"><div style="display:none;"></div><li xmlns="http://www.w3.org/1999/xhtml" xmlns:bf="http://betterform.sourceforge.net/xforms" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:xfu="http://xformsunit.org/" xmlns:xh="http://www.w3.org/1999/xhtml" xmlns:xs="http://www.w3.org/2001/XMLSchema">
                                    <span xmlns="" id="C18" controlType="group"><span></span><span xmlns="http://www.w3.org/1999/xhtml">passed</span></span>
                                    <span xmlns="" id="C19" controlType="group"><span></span><span xmlns="http://www.w3.org/1999/xhtml">failed</span><span id="C20" data-bf-class="xfControl xfOutput aDefault C17-2 repeated mediatypeText"><label>Actual : </label><span><span xmlns="http://www.w3.org/1999/xhtml" id="C20-value" tabindex="0" title=""></span></span></span><span id="C22" data-bf-class="xfControl xfOutput aDefault C17-3 repeated mediatypeText"><label>Expected : </label><span><span xmlns="http://www.w3.org/1999/xhtml" id="C22-value" tabindex="0" title=""></span></span></span></span>
                                </li></div><div xmlns="" id="C14"><div id="C24" tabindex="0"><div style="display:none;"></div><dt xmlns="http://www.w3.org/1999/xhtml">
                        <dfn>
                            <span id="C25"><label for="C25-value" id="C25-label" class=""></label><span id="C25-value" tabindex="0" title="">test-greetings</span></span>
                        </dfn>
                    </dt><dd xmlns="http://www.w3.org/1999/xhtml">
                        <ul>
                            <div xmlns="" id="C26"><div id="C28" tabindex="0"><div style="display:none;"></div><li xmlns="http://www.w3.org/1999/xhtml">
                                    <span xmlns="" id="C29"><span style="display:none;"></span>
                                        <span xmlns="http://www.w3.org/1999/xhtml">passed</span>
                                    </span>
                                    <span xmlns="" id="C30"><span style="display:none;"></span>
                                        <span xmlns="http://www.w3.org/1999/xhtml">failed</span>
                                        <span xmlns="http://www.w3.org/1999/xhtml" id="C31"><label for="C31-value" id="C31-label">Actual : </label><span id="C31-value" tabindex="0" title=""></span></span>
                                        <span xmlns="http://www.w3.org/1999/xhtml" id="C33"><label for="C33-value" id="C33-label">Expected : </label><span id="C33-value" tabindex="0" title=""></span></span>
                                    </span>
                                </li></div></div>
                        </ul>
                    </dd></div></div>
            </dl>
        </span><hr /><h3>Form</h3><p>Type your first name in the input box. <br /> If you are running XForms, the output should be displayed in
            the output area.</p><span id="C35"><label for="C35-value" id="C35-label">Please enter your first name: </label><span><input id="C35-value" name="d_C35" type="text" tabindex="0" placeholder="" value="Eric" /></span></span><br /><span id="C37"><label for="C37-value" id="C37-label">Output: </label><span id="C37-value" tabindex="0" title="">Hello Eric. We hope you like XForms!</span></span></form><div id="helpWindow" style="display:none"></div></div><span id="templates" style="display:none;"></span>
<script type="text/javascript" src="/exist/bfResources/scripts/dojo/dojo.js" data-dojo-config="has: { &#34;dojo-firebug&#34;: false, &#34;dojo-debug-messages&#34;: false }, isDebug:false, locale:'en', extraLocale: ['en'], baseUrl: '/exist/bfResources/scripts/', parseOnLoad:false, async:true, packages: [ 'dojo', 'dijit', 'dojox', 'bf' ], bf:{ sessionkey: &#34;1378998064691&#34;, contextroot:&#34;/exist&#34;, fluxPath:&#34;/exist/Flux&#34;, useDOMFocusIN:false, useDOMFocusOUT:false, useXFSelect:false, logEvents:false, unloadingMessage:&#34;You are about to leave this XForms application&#34; }"></script>
<script type="text/javascript" src="/exist/bfResources/scripts/bf/bfRelease.js"> </script>
<script type="text/javascript" src="/exist/bfResources/scripts/dwr.js"> </script>
        <script type="text/javascript">
            require(["bf/XFProcessor","bf/XFormsModelElement","dojo/_base/connect"],
                function(XFProcessor, XFormsModelElement, connect){
                        // console.debug("ready - new Session with key:", dojo.config.bf.sessionkey);

                        fluxProcessor = new XFProcessor();

                        model = new XFormsModelElement({id:"model"});

                }
            );
        </script>
</body>
* Connection #1 to host localhost left intact
* Closing connection #0
* Closing connection #1
</html>vdv@ldlc:~$

For XSLTForms and betterFORM there is no solution that doesn’t involve a browser (or the emulation of a browser if such a thing exists). This can still be done server side if we launch a browser on a virtual display such as Xvfb. To illustrate how this can be used I have added a simple script which is working on my station (running Ubuntu):

#!/bin/bash
#
export PATH=/usr/local/bin:/usr/bin:/bin
export HOME=/var/lib/tomcat7/

# env

URL=$1

mkdir /tmp/xvfb
Xvfb :2 -screen 0 800x600x24 -ac -fbdir /tmp/xvfb/ &
export DISPLAY=:2

# see http://stackoverflow.com/questions/5161193/bash-script-that-kills-a-child-process-after-a-given-timeout
( cmdpid=$BASHPID; (sleep 5; kill $cmdpid) & exec firefox -private -no-remote "$URL" )
kill %1

browser-run.sh

This script can be run in an Orbeon pipeline using the undocumented “execute processor” and the current version of XForms Unit includes the ability to run a test suite using this method, for XSLTForms:

vdv@ldlc:~$ curl -Lv http://localhost:8080/orbeon/xformsunit/suite/hello-world/server-results?implementation=xsltforms
* About to connect() to localhost port 8080 (#0)
*   Trying 127.0.0.1... connected
> GET /orbeon/xformsunit/suite/hello-world/server-results?implementation=xsltforms HTTP/1.1
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: localhost:8080
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Last-Modified: Thu, 12 Sep 2013 15:25:44 GMT
< Expires: Thu, 12 Sep 2013 15:25:44 GMT
< Cache-Control: private, max-age=0
< Content-Type: application/xml;charset=utf-8
< Content-Length: 644
< Date: Thu, 12 Sep 2013 15:25:44 GMT
< 
<?xml version="1.0" encoding="utf-8"?><suite><case id="test-greetings"><title id="d128e12">Test that greetings are correctly set</title><controller.setvalue ref="instance('instance')/PersonGivenName" id="d128e15">Eric</controller.setvalue><model.assertEqual id="d128e18" passed="true"><actual ref="instance('instance')/Greetings" id="d128e20">Hello Eric. We hope you like XForms!</actual><expected id="d128e22">Hello Eric. We hope you like XForms!</expected><message id="d128e25">The greetings should be the concatenation of "Hello ", the given name and ". We hope you like
* Connection #0 to host localhost left intact
* Closing connection #0
                XForms!".</message></model.assertEqual></case></suite>vdv@ldlc:~$

And for betterFORM:

vdv@ldlc:~/projects/xformsunit$ curl -Lv http://localhost:8080/orbeon/xformsunit/suite/hello-world/server-results?implementation=betterform
* About to connect() to localhost port 8080 (#0)
*   Trying 127.0.0.1... connected
> GET /orbeon/xformsunit/suite/hello-world/server-results?implementation=betterform HTTP/1.1
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: localhost:8080
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Last-Modified: Thu, 12 Sep 2013 17:52:12 GMT
< Expires: Thu, 12 Sep 2013 17:52:12 GMT
< Cache-Control: private, max-age=0
< Content-Type: application/xml;charset=utf-8
< Content-Length: 1013
< Date: Thu, 12 Sep 2013 17:52:12 GMT
< 
<?xml version="1.0" encoding="utf-8"?><suite xmlns:xfu="http://xformsunit.org/" xmlns:xh="http://www.w3.org/1999/xhtml" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:bf="http://betterform.sourceforge.net/xforms" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xf="http://www.w3.org/2002/xforms">
                    <case id="test-greetings">
        <title id="d24e12">Test that greetings are correctly set</title>
        <controller.setvalue id="d24e15" ref="instance('instance')/PersonGivenName">Eric</controller.setvalue>
        <model.assertEqual id="d24e18" passed="true">
            <actual id="d24e20" ref="instance('instance')/Greetings">Hello Eric. We hope you like XForms!</actual>
            <expected id="d24e22">Hello Eric. We hope you like XForms!</expected>
            <message id="d24e25">The greetings should be the concatenation of "Hello ", the given name and ". We hope you like
                XForms!".</message>
        </model.assertEqual>
    </case>
* Connection #0 to host localhost left intact
* Closing connection #0
                </suite>vdv@ldlc:~/projects/xformsunit$

This is working quite nicely on my station but I am expecting this to be a source of potential troubles and consider this feature as highly experimental!

One thought on “Progress report and next steps

Leave a Reply

Your email address will not be published. Required fields are marked *