- to run the internal web server :
gulp serve - there are two ways to add new content to the web server:
- create a new file in
theSrc/internal_www/contentdirectory using the content_template, or add an example to an existing content file IF IT FITS (dont mix concerns in your files) - create a new yaml test plan file in
theSrc/test_plansdirectory using the test plan syntax guide for instructions.
- create a new file in
There are multiple objectives served by maintaining a set of internal content for each html widget:
- Allow visual regression testing : We can compare the content generated by two versions of the code to determine if anything unexpected has changed. This can be done manually via inspection of the content, or automatically using the visual regression test suite.
- Allow interaction testing : We can write test that interact with the widget in the browser and verify certain things happen and the the final state matches a visual regression snapshot
- Real time feedback during dev : This allows a workflow where I write up a config for a widget that doesn't currently support what I am trying to do, then I start writing the code to support the new feature. Every time I save my work, the widget is redrawn, so I can see in real time how my work is progressing.
- Maintain live documentation : Using the internal web server framework it is straightforward to build tutorial style documentation, and even snapshot that documentation to ensure your docs dont break. See the rhtmlPictographs tutorial section when running gulp serve in the rhtmlPictographs repo for a good example.
At first glance in the widget repos you see quite a bit of non production code so worth explaining why it is worth it. Consider the following scenario where I begin to add feature X to my widget. I add an example called feature X WIP to the internal_www content area (i.e., /theSrc/internal_www/content) area. I run gulp serve, and navigate to the feature X WIP example. It doesn't work :(. This makes sense though because I haven't even written the code yet ! I make a series of changes to implement feature X. Every time I save an update to my project files, gulp auto builds the code, and sends a reload signal to my browser. I am literally seeing the visual effects of my code changes in real time. This is really good.
The internal web server is just hosting all the files in the browser area, which is an auto generated section of the repo. Several gulp steps work in conjunction to build the content in the browser directory and serve it at http://127.0.0.1:9000. The 'important' ones are described below:
- the
compileRenderContentPageandcompileRenderIndexPagesteps compiles ES6 into ES5 for the browser. These steps also convert the compileRenderContentPage.template.js into the compileRenderContentPage.js, adding widget specific config to the generic compileRenderContentPage.template.js to create a compileRenderContentPage.js specific for the widget under test. Same process for the index file - the
copystep copies all the html and image files fromtheSrc/internal_wwwinto thebrowserarea - the
buildContentManifeststep recursively scans thebrowser/contentarea and produces a manifest of all the content files in the area. This is used to build the index page that is displayed on http://127.0.0.1. Without this step the author would need to keep this list up to date by manual updates to the index.html file. - the
connectstep starts a static content web server hosting all the files in thebrowserdirectory and makes them available on port 9000 of localhost (i.e., http://127.0.0.1:9000). - the
watchstep runs constantly and monitors all the source code and content files. Any time the files are saved, thewatchstep will rerun one of the other build steps to update the content, and then send a signal to the browser to force a page reload.
Using the content file theSrc/internal_www/content/examples/default.html as an example we will now go through how the widget gets drawn. Note this only applies when viewing a widget at http://127.0.0.1:9000 (i.e., the internal web server). For notes on how htmlwidgets work with R, see how the code works.
- the renderContentPage.js (sourced from a template file, mixed with widget config to produce the js file) script is a bundled JS file that contains all the widget code, all the dependencies, and some code that is only used in the internal web server.
- Once the web page is loaded, the renderContentPage.js code scans the HTML content for DOM elements with a
class="example". - For each example, it retrieves the widget config, and widget user state if provided, then builds a widget by calling the widget code using the same methods that the HtmlWidget library would use.
There are several features provided by renderContentPage.js that should be discussed.
It is easiest to grasp by looking at an example in the rhtmlTemplate app:
- look at the source of the example : example_of_content_page_features.html.
- After running
gulp serve(in the rhtmlTemplate project, not in the rhtmlBuildUtils project), you can view the page http://localhost:9000/content/tutorials/example_of_content_page_features.html.
The web server content features:
The default width and height of a widget is 200 x 200. Each example can specify the width and height by using the data-width and data-height attributes.
You can line up examples in a row by wrapping them in a <div class="row"> as shown in the first image below.
The widget config can be specified inline (as in 3 examples above) or as a reference to a file (as below). The widgets initial state can also be provided as a reference to a file.
If you wish to see all state updates echoed to console.log, add ?echoState=true to the URL, for example http://localhost:9000/content/examples/minimal_example.html?echoState=true
By default the config is hidden, but if the data-show-config attribute is used, then the config will be displayed above the widget.
A border can be drawn around the widget to show its boundaries by adding a data-border=true attribute to the example
A rerender control can be added by adding a data-rerender attribute to the example
Resize controls can shown by adding a data-resize-controls attribute to the example.
Any DOM that has a snapshot-name="SNAPSHOT_NAME" attribute will cause the applitools integration to take a snapshot when the visual regression test suite is run (more on this below).
Note that adding snapshot-name also changes the css display property to inline-block, which affects the layout of the content. This is done so that the width of the container is limited to the content inside, instead of the full availalbe browser width. In other words: Applitools wants a fixed box, display: inline-block provides this. As a result, it's better to wrap your examples with a new <div snapshot-name="NAME"> instead of adding the snapshot-name attribute directly to your examples.
The features provided by renderContentPage.js are listed above. Using renderContentPage.js we can write some HTML markup that causes widgets to be rendered. An alternative approach is to use renderExample.html, which will take all arguments as query parameters and render a single widget. This is useful for automated testing because we do not need to craft a HTML page to render a widget, instead we just pass arguments to renderExample.html.
When gulp serve is running the renderExample.html page is available at this URL : [http://localhost:9000/renderExample.html]
renderExample.html is in a state of flux. It used to accept the parameters listed below, but at present it will only accept a config query parameter whose value is a base64 encoded json payload. This was done to facilitate the new test_plan style specifications in rhtmlDonut. However when this change was made, backwards compatability was not maintained.
The config query param accepts these parameters:
- width: set the widget width
- height: set the widget height
- config: set the widget config : the config will be retrieved from ./theSrc/internal_www/data/<config_name>/config.json
- state: set the widget state : the state will be retrieved from ./theSrc/internal_www/data/<config_name>/.json
- rerenderControls: if set to true then add rerender controls to the page
In summary renderExample.html is used for interaction testing via the BDD suite, and to facilitate test_plan style content specification. It used to accept plain text query params, but that was removed, and may be readded in the future.





