diff --git a/Makefile b/Makefile index a1fe55cf..8ece5d0a 100644 --- a/Makefile +++ b/Makefile @@ -41,9 +41,11 @@ BROWSER_LIST?=chrome ALT_BROWSER_LIST?=chrome BASEURL?=http://192.168.99.100:9500 TEST_TYPE?=local -SPECS?=system-test/*-specs.js +SPECS?=system-test/urth-system-test-specs.js system-test/urth-core-bind-specs.js system-test/urth-viz-table-specs.js PYTHON2_SPECS?=system-test/urth-system-test-specs.js -ALT_JUPYTER_SPECS?=system-test/urth-system-test-specs.js system-test/urth-r-widgets-specs.js +ALT_JUPYTER_SPECS?=system-test/urth-system-test-specs.js +R_SPECS?=system-test/urth-r-widgets-specs.js +SCALA_SPECS?=system-test/urth-scala-widgets-specs.js ALT_JUPYTER_VERSION?=4.2 PYTHON?=python3 TEST_MSG?="Starting system tests" @@ -394,7 +396,17 @@ system-test-alt-jupyter: TEST_MSG="Starting system tests for Jupyter $(ALT_JUPYT system-test-alt-jupyter: @TEST_MSG=$(TEST_MSG) TEST_TYPE=$(TEST_TYPE) BROWSER_LIST="$(ALT_BROWSER_LIST)" JUPYTER=$(JUPYTER) SPECS="$(SPECS)" BASEURL=$(BASEURL) $(MAKE) run-test -system-test-all: system-test-python3 system-test-python2 system-test-alt-jupyter +system-test-scala: SPECS:=$(SCALA_SPECS) +system-test-scala: TEST_MSG="Starting system tests for Scala" +system-test-scala: + @TEST_MSG=$(TEST_MSG) TEST_TYPE=$(TEST_TYPE) BROWSER_LIST="$(ALT_BROWSER_LIST)" JUPYTER=$(JUPYTER) SPECS="$(SPECS)" BASEURL=$(BASEURL) $(MAKE) run-test + +system-test-r: SPECS:=$(R_SPECS) +system-test-r: TEST_MSG="Starting system tests for R" +system-test-r: + @TEST_MSG=$(TEST_MSG) TEST_TYPE=$(TEST_TYPE) BROWSER_LIST="$(ALT_BROWSER_LIST)" JUPYTER=$(JUPYTER) SPECS="$(SPECS)" BASEURL=$(BASEURL) $(MAKE) run-test + +system-test-all: system-test-python3 system-test-python2 system-test-alt-jupyter system-test-scala system-test-r start-selenium: node_modules stop-selenium @echo "Installing and starting Selenium Server..." diff --git a/etc/notebooks/tests/Walkthrough-Scala.ipynb b/etc/notebooks/tests/Walkthrough-Scala.ipynb new file mode 100644 index 00000000..38b857ee --- /dev/null +++ b/etc/notebooks/tests/Walkthrough-Scala.ipynb @@ -0,0 +1,426 @@ +{ + "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://localhost:8888/nbextensions/declarativewidgets/urth-widgets.jar" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "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", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "lazy val initChannelWatch = () => {\n", + " channel(\"c\").watch(\"aSomething\", on_aSomething_change)\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%html\n", + "" + ] + }, + { + "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": "Apache_Toree", + "language": "", + "name": "apache_toree" + }, + "language_info": { + "name": "scala" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/package.json b/package.json index 037c91fe..4aef09ec 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "minimist": "latest", "selenium-standalone": "latest", "web-component-tester": "^4", - "vulcanize": "1.14.8" + "vulcanize": "1.14.8", + "bluebird" : "3.3.5" }, "config": {}, "scripts": { diff --git a/system-test/urth-core-bind-specs.js b/system-test/urth-core-bind-specs.js index 76c493af..2863344a 100644 --- a/system-test/urth-core-bind-specs.js +++ b/system-test/urth-core-bind-specs.js @@ -7,7 +7,7 @@ var boilerplate = new Boilerplate(); describe('Urth Core Bind', function() { - boilerplate.setup(this.title, '/notebooks/tests/urth-core-bind.ipynb'); + boilerplate.setup(this.title, '/notebooks/tests/urth-core-bind.ipynb', 4); it('should wait for dependency to load', function(done) { // Using a random number to protect against the possibility of a previous diff --git a/system-test/urth-r-widgets-specs.js b/system-test/urth-r-widgets-specs.js index a7628d09..44e1f608 100644 --- a/system-test/urth-r-widgets-specs.js +++ b/system-test/urth-r-widgets-specs.js @@ -6,7 +6,7 @@ var Boilerplate = require('./utils/boilerplate'); var boilerplate = new Boilerplate(); process.env.PYTHON != "python2" && describe('Widgets R System Test', function() { - boilerplate.setup(this.title, '/notebooks/tests/urth-r-widgets.ipynb'); + boilerplate.setup(this.title, '/notebooks/tests/urth-r-widgets.ipynb', 8); it('should print the result of a Function Widget invocation', function(done) { boilerplate.browser diff --git a/system-test/urth-scala-widgets-specs.js b/system-test/urth-scala-widgets-specs.js new file mode 100644 index 00000000..804c853c --- /dev/null +++ b/system-test/urth-scala-widgets-specs.js @@ -0,0 +1,68 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +var wd = require('wd'); +var Boilerplate = require('./utils/boilerplate'); +var boilerplate = new Boilerplate(); + +process.env.PYTHON != "python2" && describe('Widgets Scala System Test', function() { + boilerplate.setup(this.title, '/notebooks/tests/Walkthrough-Scala.ipynb', 11); + + 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 + .elementByXPath('//button[text()="initChannelWatch"]').click() + .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); + }); +}); \ No newline at end of file diff --git a/system-test/urth-system-test-specs.js b/system-test/urth-system-test-specs.js index 810e7b3e..fdec215a 100644 --- a/system-test/urth-system-test-specs.js +++ b/system-test/urth-system-test-specs.js @@ -5,8 +5,8 @@ var wd = require('wd'); var Boilerplate = require('./utils/boilerplate'); var boilerplate = new Boilerplate(); -describe('Widgets System Test', function() { - boilerplate.setup(this.title, '/notebooks/tests/Walkthrough.ipynb'); +describe('Widgets Python System Test', function() { + boilerplate.setup(this.title, '/notebooks/tests/Walkthrough.ipynb', 13); it('should not execute Urth.whenReady API until components have upgraded', function(done) { boilerplate.browser @@ -61,6 +61,8 @@ describe('Widgets System Test', function() { .type(boilerplate.SPECIAL_KEYS.Enter) // Needed for IE .type('watched message') .waitForElementById('test4', wd.asserters.textInclude('watched message'), 10000) + .elementsByCssSelector('div.output_area').nth(7) + .click() .nodeify(done); }); @@ -86,3 +88,4 @@ describe('Widgets System Test', function() { .nodeify(done); }); }); + diff --git a/system-test/urth-viz-table-specs.js b/system-test/urth-viz-table-specs.js index 2aabf7ac..da773944 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', 4); 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 6d0440cd..f5ffabf9 100644 --- a/system-test/utils/boilerplate.js +++ b/system-test/utils/boilerplate.js @@ -1,6 +1,6 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. - +var Promise = require('bluebird'); var wd = require('wd'); require('colors'); var chai = require('chai'); @@ -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,22 @@ 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 + .elementsByCssSelector('div.output_area').then(function(nodes) { + console.log("output areas visible: ", nodes.length, "/", outputCount) + nodes.should.have.length.above(outputCount-1); + 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 +106,7 @@ Boilerplate.prototype.setup = function(testName, startingURL){ this.browser.init(desired) .get(startingURL || '/') - .waitForElementByCssSelector('#kernel_indicator_icon.kernel_idle_icon', wd.asserters.isDisplayed, 10000) + .waitForElementByCssSelector('#kernel_indicator_icon.kernel_idle_icon', wd.asserters.isDisplayed, 80000) .waitForElementByLinkText('Cell', wd.asserters.isDisplayed, 10000) .safeExecute('localStorage.clear()') .elementByLinkText('Cell') @@ -102,6 +122,7 @@ 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) + .waitFor(outputAsserter, 250000, 1000) .nodeify(done); }.bind(this));