From 7cb1e6f821ea4ad4c23a9d4e5b54dd46ec1d5b67 Mon Sep 17 00:00:00 2001 From: Sean Welleck Date: Tue, 15 Dec 2015 10:48:01 -0600 Subject: [PATCH 1/7] System tests for Scala widgets. (c) Copyright IBM Corp. 2016 --- notebooks/tests/Walkthrough-Scala.ipynb | 400 ++++++++++++++++++++++++ system-test/urth-system-test-specs.js | 63 +++- system-test/utils/boilerplate.js | 13 +- 3 files changed, 470 insertions(+), 6 deletions(-) create mode 100644 notebooks/tests/Walkthrough-Scala.ipynb diff --git a/notebooks/tests/Walkthrough-Scala.ipynb b/notebooks/tests/Walkthrough-Scala.ipynb new file mode 100644 index 00000000..ffe1918f --- /dev/null +++ b/notebooks/tests/Walkthrough-Scala.ipynb @@ -0,0 +1,400 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Declarative Widgets Walkthrough" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Built on top of [IPyWidgets](https://github.com/ipython/ipywidgets) and combined with [Polymer](https://www.polymer-project.org/1.0/) and [Web Components](http://webcomponents.org/), these widgets use a declarative syntax for creating interactive areas that are usable throughout a notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Widgets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First we need to add, import, and initialize the widget system:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "// modify to IP and Port of this notebook server\n", + "%addjar http://192.168.99.100:9500/nbextensions/urth_widgets/urth-widgets.jar" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import urth.widgets._\n", + "initWidgets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's start by creating a \"Hello world\" function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def greet(name: String = \"world\") = s\"Hello ${name}!\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we'll bind to this function and modify the name field to update our greeting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%html\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note how the default value is set based on the argument passed into the function. We pass the name of our function to the `ref` parameter, then explicitly set the arguments with the `arg-` prefix, and finally bind our output (`result`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%html\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Try changing the `Name` argument and clicking the button above to call the `greet` function. The resulting greeting updates based on the current `Name` field." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Click [here](../examples/urth-core-function.ipynb) to learn more about ``" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also bind to variables over independent channels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%html\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%html\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Try modifying the `user` defined in channel `a`. This change will not impact the user defined in channel `b`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now to something a bit more complex. What if you want to have Scala code that reacts to changes in a value on a template. Lets start with the template below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%html\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can create a Scala function that will watch for changes to the value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import urth.widgets.WidgetChannels.channel\n", + "\n", + "val on_aSomething_change = (oldVal: Option[String], newVal: String) => {\n", + " val msg = s\"Hello from on_aNumber_change! Got ${newVal}\"\n", + " channel(\"c\").set(\"message\", msg)\n", + "}\n", + " \n", + "channel(\"c\").watch(\"aSomething\", on_aSomething_change)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lets create a template where we can set a message." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%html\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now when you type something on the input box, it triggers the Scala function `on_aSomething_change`. This function can then also set values on the channel by using the `set` method. Notice that the template with `{{message}}` is getting updated." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Click [here](../examples/urth-core-bind.ipynb) to learn more about ``" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Support for Spark DataFrames is provided. Below is a DataFrame with some basic contact information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "val sqlContext = new org.apache.spark.sql.SQLContext(sc)\n", + "import sqlContext.implicits._" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "case class Contact(name: String, email: String)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "val df = sqlContext.createDataFrame(Seq(\n", + " Contact(\"Jane Doe\", \"jane@doe.com\"), \n", + " Contact(\"John Doe\", \"john@doe.com\")))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below, we print out the contents of the DataFrame in a more readable format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%html\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By setting the `auto` keyword, the resulting output will update whenever the DataFrame is modified. Try changing which set of contact information is used." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "val df = sqlContext.createDataFrame(Seq(\n", + " Contact(\"Richard Roe\", \"richard@roe.com\"),\n", + " Contact(\"Bob Murphy\", \"bob@murphy.com\")))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Click [here](../examples/urth-core-dataframe.ipynb) to learn more about ``" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also import web components. Below, we bring in the `paper-input` element from the [Polymer Catalog](https://elements.polymer-project.org/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%html\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Click [here](../examples/urth-core-import.ipynb) to learn more about ``" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Scala 2.10.4", + "language": "scala", + "name": "scala" + }, + "language_info": { + "name": "scala" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/system-test/urth-system-test-specs.js b/system-test/urth-system-test-specs.js index 810e7b3e..d3f5bd34 100644 --- a/system-test/urth-system-test-specs.js +++ b/system-test/urth-system-test-specs.js @@ -5,7 +5,7 @@ var wd = require('wd'); var Boilerplate = require('./utils/boilerplate'); var boilerplate = new Boilerplate(); -describe('Widgets System Test', function() { +describe('Widgets Python System Test', function() { boilerplate.setup(this.title, '/notebooks/tests/Walkthrough.ipynb'); it('should not execute Urth.whenReady API until components have upgraded', function(done) { @@ -86,3 +86,64 @@ describe('Widgets System Test', function() { .nodeify(done); }); }); + +describe('Widgets Scala System Test', function() { + boilerplate.setup(this.title, '/notebooks/tests/Walkthrough-Scala.ipynb'); + + var timeout = 30000; + + it('should print the correct variable that is used for urth-core-function', function(done) { + + boilerplate.browser + .elementsByCssSelector('div.code_cell').nth(5) + .elementByXPath('//button[text()="invoke"]').click() + .waitForElementById('test1', wd.asserters.textInclude('world'), timeout) + .nodeify(done); + }); + + it('should bind variable to channel a', function(done) { + boilerplate.browser + .elementsByCssSelector('div.code_cell').nth(6) + .elementByCssSelector('>', 'input') + .type('A') + .waitForElementById('test2', wd.asserters.textInclude('A'), timeout) + .nodeify(done); + }); + + it('should bind variable to channel b', function(done) { + boilerplate.browser + .elementsByCssSelector('div.code_cell').nth(7) + .elementByCssSelector('>', 'input') + .type('B') + .waitForElementById('test3', wd.asserters.textInclude('B'), timeout) + .nodeify(done); + }); + + it('should bind variables to channels independently', function(done) { + boilerplate.browser + .elementsByCssSelector('div.code_cell').nth(6) + .elementByCssSelector('>', 'input') + .type('2') + .elementByCssSelector('#test2') + .text().should.eventually.include('A2') + .waitForElementById('test2', wd.asserters.textInclude('A2'), timeout) + .waitForElementById('test3', wd.asserters.textInclude('B'), timeout) + .nodeify(done); + }); + + it('should watch for changes in a watched variable', function(done) { + boilerplate.browser + .elementsByCssSelector('div.code_cell').nth(8) + .elementByCssSelector('>', 'input') + .type('watched message') + .waitForElementById('test4', wd.asserters.textInclude('watched message'), timeout) + .nodeify(done); + }); + + it('should update output when DataFrame is modified and set to auto', function(done) { + boilerplate.browser + .elementsByCssSelector('div.code_cell').nth(14) + .waitForElementByClassName('test5', wd.asserters.textInclude('Richard Roe'), timeout) + .nodeify(done); + }); +}); diff --git a/system-test/utils/boilerplate.js b/system-test/utils/boilerplate.js index 6d0440cd..12deb9b2 100644 --- a/system-test/utils/boilerplate.js +++ b/system-test/utils/boilerplate.js @@ -84,15 +84,18 @@ Boilerplate.prototype.setup = function(testName, startingURL){ desired.name = testName ? 'Urth Widgets System Test - ' + testName : 'Urth Widgets System Test'; + var kernelStartTimeout = 200000; + var defaultTimeout = 10000; + var runAllCompletionTime = 50000; this.browser.init(desired) .get(startingURL || '/') - .waitForElementByCssSelector('#kernel_indicator_icon.kernel_idle_icon', wd.asserters.isDisplayed, 10000) - .waitForElementByLinkText('Cell', wd.asserters.isDisplayed, 10000) + .waitForElementByCssSelector("#kernel_indicator_icon.kernel_idle_icon", wd.asserters.isDisplayed, kernelStartTimeout) + .waitForElementByLinkText("Cell", wd.asserters.isDisplayed, defaultTimeout) .safeExecute('localStorage.clear()') - .elementByLinkText('Cell') + .elementByLinkText("Cell") .click() - .waitForElementByLinkText('Run All', wd.asserters.isDisplayed, 10000) - .elementByLinkText('Run All') + .waitForElementByLinkText("Run All", wd.asserters.isDisplayed, defaultTimeout) + .elementByLinkText("Run All") .click() .eval('!!document.body.createShadowRoot', function(err, value) { this.browserSupportsShadowDOM = value; From 3c2c56f5f6eb3c17aa2ec8478c3ddb4b703b29c6 Mon Sep 17 00:00:00 2001 From: peller Date: Wed, 20 Jan 2016 23:48:38 -0500 Subject: [PATCH 2/7] Add custom asserter to setup to waitFor all output_areas before proceeding with tests use localhost:8888 for addjar (c) Copyright IBM Corp. 2016 --- notebooks/tests/Walkthrough-Scala.ipynb | 2 +- system-test/urth-system-test-specs.js | 4 ++-- system-test/urth-viz-table-specs.js | 2 +- system-test/utils/boilerplate.js | 25 +++++++++++++++++++++++-- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/notebooks/tests/Walkthrough-Scala.ipynb b/notebooks/tests/Walkthrough-Scala.ipynb index ffe1918f..d20dee00 100644 --- a/notebooks/tests/Walkthrough-Scala.ipynb +++ b/notebooks/tests/Walkthrough-Scala.ipynb @@ -44,7 +44,7 @@ "outputs": [], "source": [ "// modify to IP and Port of this notebook server\n", - "%addjar http://192.168.99.100:9500/nbextensions/urth_widgets/urth-widgets.jar" + "%addjar http://localhost:8888/nbextensions/urth_widgets/urth-widgets.jar" ] }, { diff --git a/system-test/urth-system-test-specs.js b/system-test/urth-system-test-specs.js index d3f5bd34..ce04af61 100644 --- a/system-test/urth-system-test-specs.js +++ b/system-test/urth-system-test-specs.js @@ -6,7 +6,7 @@ var Boilerplate = require('./utils/boilerplate'); var boilerplate = new Boilerplate(); describe('Widgets Python System Test', function() { - boilerplate.setup(this.title, '/notebooks/tests/Walkthrough.ipynb'); + boilerplate.setup(this.title, '/notebooks/tests/Walkthrough.ipynb', 8); it('should not execute Urth.whenReady API until components have upgraded', function(done) { boilerplate.browser @@ -88,7 +88,7 @@ describe('Widgets Python System Test', function() { }); describe('Widgets Scala System Test', function() { - boilerplate.setup(this.title, '/notebooks/tests/Walkthrough-Scala.ipynb'); + boilerplate.setup(this.title, '/notebooks/tests/Walkthrough-Scala.ipynb', 8); var timeout = 30000; diff --git a/system-test/urth-viz-table-specs.js b/system-test/urth-viz-table-specs.js index 2aabf7ac..02652a81 100644 --- a/system-test/urth-viz-table-specs.js +++ b/system-test/urth-viz-table-specs.js @@ -22,7 +22,7 @@ describe('Urth Viz Table Test', function() { .catch(tagChaiAssertionError); }; - boilerplate.setup(this.title, '/notebooks/tests/urth-viz-table.ipynb'); + boilerplate.setup(this.title, '/notebooks/tests/urth-viz-table.ipynb', 3); it('should run all cells and find a handsontable in the 3rd output area', function(done) { boilerplate.browser diff --git a/system-test/utils/boilerplate.js b/system-test/utils/boilerplate.js index 12deb9b2..2d6dabd2 100644 --- a/system-test/utils/boilerplate.js +++ b/system-test/utils/boilerplate.js @@ -18,6 +18,13 @@ chai.use(chaiAsPromised); chai.should(); chaiAsPromised.transferPromiseness = wd.transferPromiseness; +// tagging chai assertion errors for retry +var tagChaiAssertionError = function(err) { + // throw error and tag as retriable to poll again + err.retriable = err instanceof chai.AssertionError; + throw err; +}; + // Configure webdriver wd.configureHttp({ timeout: 60000, @@ -67,9 +74,21 @@ var Boilerplate = function(){ * Setups the before and after calls for each of your tests. The boilerplate * will start each test on startingURL, which is a relative path to the resource to load. */ -Boilerplate.prototype.setup = function(testName, startingURL){ +Boilerplate.prototype.setup = function(testName, startingURL, outputCount){ var that = this; + var outputAsserter = new wd.Asserter( + function(target) { // browser or el + return target + .elementsByCss('div.output_area').then(function(nodes) { + nodes.should.have.length(outputCount); + return target; // this will be returned by waitFor + // and ignored by waitForElement. + }) + .catch(tagChaiAssertionError); // tag errors for retry in catch. + } + ); + before(function(done){ if (args.verbose) { // optional logging @@ -86,7 +105,7 @@ Boilerplate.prototype.setup = function(testName, startingURL){ var kernelStartTimeout = 200000; var defaultTimeout = 10000; - var runAllCompletionTime = 50000; + var runAllCompletionTimeout = 50000; this.browser.init(desired) .get(startingURL || '/') .waitForElementByCssSelector("#kernel_indicator_icon.kernel_idle_icon", wd.asserters.isDisplayed, kernelStartTimeout) @@ -105,6 +124,8 @@ Boilerplate.prototype.setup = function(testName, startingURL){ .waitForConditionInBrowser('window.Urth && Urth.kernel && Urth.kernel.is_connected()', 10000) .waitForElementByCssSelector('#kernel_indicator_icon.kernel_idle_icon', wd.asserters.isDisplayed, 20000) .waitForConditionInBrowser('typeof Urth.whenReady === "function"', 10000) + .elementByCssSelector('div.code_cell .input').click() // to keep the first code cell in view on sauce + .waitFor(outputAsserter, runAllCompletionTimeout, 1000) .nodeify(done); }.bind(this)); From 7781cfca3f5dd83122115f3f5de283b0ac12e6dc Mon Sep 17 00:00:00 2001 From: peller Date: Mon, 1 Feb 2016 11:24:55 -0500 Subject: [PATCH 3/7] relocated test to etc directory (c) Copyright IBM Corp. 2016 --- {notebooks => etc/notebooks}/tests/Walkthrough-Scala.ipynb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {notebooks => etc/notebooks}/tests/Walkthrough-Scala.ipynb (100%) diff --git a/notebooks/tests/Walkthrough-Scala.ipynb b/etc/notebooks/tests/Walkthrough-Scala.ipynb similarity index 100% rename from notebooks/tests/Walkthrough-Scala.ipynb rename to etc/notebooks/tests/Walkthrough-Scala.ipynb From 07807210a3b7306a3a6c9b11e41cb4913ad8b75a Mon Sep 17 00:00:00 2001 From: peller Date: Tue, 2 Feb 2016 23:29:09 -0500 Subject: [PATCH 4/7] Remove '-' from polymer substitution to prevent JSON parse error (c) Copyright IBM Corp. 2016 --- etc/notebooks/tests/Walkthrough-Scala.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/notebooks/tests/Walkthrough-Scala.ipynb b/etc/notebooks/tests/Walkthrough-Scala.ipynb index d20dee00..e21a0bd9 100644 --- a/etc/notebooks/tests/Walkthrough-Scala.ipynb +++ b/etc/notebooks/tests/Walkthrough-Scala.ipynb @@ -320,8 +320,8 @@ "source": [ "%%html\n", "