diff --git a/html/continuous-loop.html b/html/continuous-loop.html index 2f05614..d0bda64 100644 --- a/html/continuous-loop.html +++ b/html/continuous-loop.html @@ -12,7 +12,7 @@ - + diff --git a/html/no-test.html b/html/no-test.html index 1f4942e..1eec117 100644 --- a/html/no-test.html +++ b/html/no-test.html @@ -11,13 +11,10 @@ - - - + + + + No current test available, will recheck soon. diff --git a/html/pageload-test.html b/html/pageload-test.html index c810db6..6aa18d2 100644 --- a/html/pageload-test.html +++ b/html/pageload-test.html @@ -12,8 +12,9 @@ - - - + + + + diff --git a/html/qunit-test.html b/html/qunit-test.html index 54cb2ab..adf5302 100644 --- a/html/qunit-test.html +++ b/html/qunit-test.html @@ -12,8 +12,9 @@ - - - + + + + diff --git a/html/sim-test.html b/html/sim-test.html index 5698869..c81e57b 100644 --- a/html/sim-test.html +++ b/html/sim-test.html @@ -12,8 +12,9 @@ - - - + + + + diff --git a/html/wrapper-test.html b/html/wrapper-test.html new file mode 100644 index 0000000..4ffb5e3 --- /dev/null +++ b/html/wrapper-test.html @@ -0,0 +1,20 @@ + + + + + + + + Wrapper Test + + + + + + + + + diff --git a/js/client/client.js b/js/client/client.js new file mode 100644 index 0000000..9e8b950 --- /dev/null +++ b/js/client/client.js @@ -0,0 +1,155 @@ +// Copyright 2017-2020, University of Colorado Boulder + +/* + * Common functions used to communicate from test wrappers to continuous-loop.html (assumed to be the parent frame). + * + * @author Jonathan Olson + */ + +'use strict'; + +// Because ES5 for IE11 compatibility +/* eslint-disable no-var */ + +var aquaOptions = QueryStringMachine.getAll( { + testInfo: { + type: 'string', + defaultValue: '{}' + }, + width: { + type: 'number', + defaultValue: 512 + }, + height: { + type: 'number', + defaultValue: 384 + } +} ); + +var sentMessage = false; +var iframe = null; + +window.aqua = { + // @public {Object} + options: aquaOptions, + + /** + * Creates an iframe, adds it to the body, and returns it + * @public + * + * @returns {HTMLIFrameElement} + */ + createFrame: function() { + iframe = document.createElement( 'iframe' ); + iframe.setAttribute( 'frameborder', '0' ); + iframe.setAttribute( 'seamless', '1' ); + iframe.setAttribute( 'width', '' + aquaOptions.width ); + iframe.setAttribute( 'height', '' + aquaOptions.height ); + document.body.appendChild( iframe ); + return iframe; + }, + + /** + * Moves to the next test, clearing out the iframe. + * @public + */ + simpleFinish: function() { + if ( iframe ) { + iframe.src = 'about:blank'; + } + aqua.nextTest(); + }, + + /** + * Records a pass for a pass/skip/fail test. + * @public + * + * @param {string} [message] + */ + simplePass: function( message ) { + if ( sentMessage ) { return; } + sentMessage = true; + + aqua.testPass( message ); + aqua.simpleFinish(); + }, + + /** + * Records a skip for a pass/skip/fail test. + * @public + */ + simpleSkip: function() { + if ( sentMessage ) { return; } + sentMessage = true; + + aqua.simpleFinish(); + }, + + /** + * Records a fail for a pass/skip/fail test. + * @public + * + * @param {string} message + */ + simpleFail: function( message ) { + if ( sentMessage ) { return; } + sentMessage = true; + + aqua.testFail( ( iframe ? iframe.src + '\n' : '' ) + message ); + aqua.simpleFinish(); + }, + + /** + * Sends a post message. + * @private + * + * @param {Object} message + */ + sendMessage: function( message ) { + ( window.parent !== window ) && window.parent.postMessage( JSON.stringify( message ), '*' ); + }, + + /** + * Sends a test pass. + * @public + * + * @param {string|undefined} message + */ + testPass: function( message ) { + aqua.sendMessage( { + type: 'test-pass', + message: message, + testInfo: JSON.parse( aquaOptions.testInfo ) + } ); + console.log( '[PASS] ' + message ); + }, + + /** + * Sends a test failure. + * @public + * + * @param {string|undefined} message + */ + testFail: function( message ) { + // Don't send timeouts as failures, since it doesn't usually indicate an underlying problem + if ( message.indexOf( 'errors.html#timeout' ) < 0 ) { + aqua.sendMessage( { + type: 'test-fail', + message: message, + testInfo: JSON.parse( aquaOptions.testInfo ) + } ); + } + console.log( '[FAIL] ' + message ); + }, + + /** + * Sends a request to move to the next test + * @public + */ + nextTest: function() { + aqua.sendMessage( { + type: 'test-next' + } ); + console.log( '[NEXT TEST]' ); + } +}; diff --git a/js/client/no-test.js b/js/client/no-test.js new file mode 100644 index 0000000..d96ecca --- /dev/null +++ b/js/client/no-test.js @@ -0,0 +1,23 @@ +// Copyright 2020, University of Colorado Boulder + +/* + * Does nothing for a certain amount of time, then goes to the next test + * + * @author Jonathan Olson + */ + +'use strict'; + +// Because ES5 for IE11 compatibility +/* eslint-disable no-var */ + +var options = QueryStringMachine.getAll( { + duration: { + type: 'number', + defaultValue: 10000 + } +} ); + +setTimeout( function() { + aqua.nextTest(); +}, options.duration ); diff --git a/js/client/pageload-test.js b/js/client/pageload-test.js new file mode 100644 index 0000000..6ba743c --- /dev/null +++ b/js/client/pageload-test.js @@ -0,0 +1,49 @@ +// Copyright 2018-2020, University of Colorado Boulder + +/* + * Runs arbitrary content in an iframe (that presumably loads pageload-connector.js) and reports if it successfully + * loads and runs for a short amount of time without erroring + * + * @author Jonathan Olson + */ + +'use strict'; + +// Because ES5 for IE11 compatibility +/* eslint-disable no-var */ + +var options = QueryStringMachine.getAll( { + url: { + type: 'string', + defaultValue: '' + }, + // If the page doesn't report back by this {number} of milliseconds, then report a failure. + duration: { + type: 'number', + defaultValue: 30000 + } +} ); + +var iframe = aqua.createFrame(); +iframe.src = options.url; + +setTimeout( function() { + aqua.simpleFail( 'Did not load in the time allowed: ' + options.duration + 'ms' ); +}, options.duration ); + +// handling messages from the page +window.addEventListener( 'message', function( evt ) { + if ( typeof evt.data !== 'string' ) { + return; + } + + var data = JSON.parse( evt.data ); + + // Sent by Joist due to the postMessage* query parameters + if ( data.type === 'pageload-load' ) { + aqua.simplePass(); + } + else if ( data.type === 'pageload-error' ) { + aqua.simpleFail( data.message + '\n' + data.stack ); + } +} ); diff --git a/js/qunit-test.js b/js/client/qunit-test.js similarity index 77% rename from js/qunit-test.js rename to js/client/qunit-test.js index 8bfe96f..2fbc961 100644 --- a/js/qunit-test.js +++ b/js/client/qunit-test.js @@ -8,7 +8,10 @@ 'use strict'; -const options = QueryStringMachine.getAll( { +// Because ES5 for IE11 compatibility +/* eslint-disable no-var */ + +var options = QueryStringMachine.getAll( { url: { type: 'string', defaultValue: '' @@ -19,21 +22,15 @@ const options = QueryStringMachine.getAll( { } } ); -const iframe = document.createElement( 'iframe' ); -iframe.setAttribute( 'frameborder', '0' ); -iframe.setAttribute( 'seamless', '1' ); -iframe.setAttribute( 'width', '512' ); -iframe.setAttribute( 'height', '384' ); -document.body.appendChild( iframe ); - +var iframe = aqua.createFrame(); iframe.src = options.url; // Since QUnit doesn't give us an accurate "done" message, we just tally pass/fail counts and wait for a certain amount of time to report back. -let passed = 0; -let failed = 0; -let message = ''; +var passed = 0; +var failed = 0; +var message = ''; -const done = function() { +var done = function() { if ( id !== null ) { message = iframe.src + '\n' + passed + ' out of ' + ( passed + failed ) + ' tests passed. ' + failed + ' failed.\n' + message; if ( passed > 0 && failed === 0 ) { @@ -48,14 +45,14 @@ const done = function() { }; // Supports old tests (which do not know when they are done) -let id = setTimeout( done, options.duration ); +var id = setTimeout( done, options.duration ); window.addEventListener( 'message', function( evt ) { if ( typeof evt.data !== 'string' ) { return; } - const data = JSON.parse( evt.data ); + var data = JSON.parse( evt.data ); // Sent from all of our QUnit wrappers if ( data.type === 'qunit-test' ) { diff --git a/js/client/sim-test.js b/js/client/sim-test.js new file mode 100644 index 0000000..c491dd0 --- /dev/null +++ b/js/client/sim-test.js @@ -0,0 +1,84 @@ +// Copyright 2017-2020, University of Colorado Boulder + +/** + * Runs simulation tests in an iframe, and passes results to our parent frame (continuous-loop.html). + * + * @author Jonathan Olson + */ + +'use strict'; + +// Because ES5 for IE11 compatibility +/* eslint-disable no-var */ + +var options = QueryStringMachine.getAll( { + url: { + type: 'string', + defaultValue: '' + }, + duration: { + type: 'number', + defaultValue: 120000 + }, + + // By default, if the load doesn't happen, we'll just skip the test + failIfNoLoad: { + type: 'flag' + }, + + simQueryParameters: { + type: 'string', + defaultValue: '' + } +} ); + +// Add those two to our query parameters, so we get load/error messages +var iframe = aqua.createFrame(); +iframe.src = QueryStringMachine.appendQueryStringArray( options.url, [ + '?continuousTest=' + encodeURIComponent( aqua.options.testInfo ), + options.simQueryParameters +] ); + +var failPrefix = ( options.simQueryParameters ? ( 'Query: ' + options.simQueryParameters + '\n' ) : '' ); + +var hasLoaded = false; + +setTimeout( function() { + if ( hasLoaded ) { + aqua.simplePass(); // Only pass the 'run' if it loads AND doesn't error for the entire duration + } + else { + if ( options.failIfNoLoad ) { + aqua.simpleFail( failPrefix + 'did not load in ' + options.duration + 'ms' ); + } + else { + aqua.simpleSkip(); + } + } +}, options.duration ); + +var testInfo = JSON.parse( aqua.options.testInfo ); + +// handling messages from sims +window.addEventListener( 'message', function( evt ) { + if ( typeof evt.data !== 'string' ) { + return; + } + var data = JSON.parse( evt.data ); + + // Filter out any message that isn't directly from this test + if ( data.continuousTest && _.isEqual( testInfo, data.continuousTest ) ) { + console.log( data.type ); + + // Sent by Joist due to the postMessage* query parameters + if ( data.type === 'continuous-test-error' ) { + aqua.simpleFail( failPrefix + data.message + '\n' + data.stack ); + } + else if ( data.type === 'continuous-test-unload' ) { + aqua.simpleFail( failPrefix + 'Unloaded frame before complete, window.location probably changed' ); + } + else if ( data.type === 'continuous-test-load' ) { + hasLoaded = true; + } + } +} ); diff --git a/js/client/wrapper-test.js b/js/client/wrapper-test.js new file mode 100644 index 0000000..758b28c --- /dev/null +++ b/js/client/wrapper-test.js @@ -0,0 +1,77 @@ +// Copyright 2017-2020, University of Colorado Boulder + +/** + * Runs a phet-io wrapper test in an iframe, and passes results to our parent frame (continuous-loop.html). + * + * @author Jonathan Olson + */ + +'use strict'; + +// Because ES5 for IE11 compatibility +/* eslint-disable no-var */ + +var options = QueryStringMachine.getAll( { + url: { + type: 'string', + defaultValue: '' + }, + duration: { + type: 'number', + defaultValue: 60000 + }, + + // By default, if the load doesn't happen, we'll just skip the test + failIfNoLoad: { + type: 'flag' + } +} ); + +// Add those two to our query parameters, so we get load/error messages +var iframe = aqua.createFrame(); +iframe.src = QueryStringMachine.appendQueryStringArray( options.url, [ + '?wrapperContinuousTest=' + encodeURIComponent( aqua.options.testInfo ) +] ); + +var hasLoaded = false; + +setTimeout( function() { + if ( hasLoaded ) { + aqua.simplePass(); // Only pass the 'run' if it loads AND doesn't error for the entire duration + } + else { + if ( options.failIfNoLoad ) { + aqua.simpleFail( 'did not load in ' + options.duration + 'ms' ); + } + else { + aqua.simpleSkip(); + } + } +}, options.duration ); + +var testInfo = JSON.parse( aqua.options.testInfo ); + +// handling messages from sims +window.addEventListener( 'message', function( evt ) { + if ( typeof evt.data !== 'string' ) { + return; + } + var data = JSON.parse( evt.data ); + + // Filter out any message that isn't directly from this test + if ( data.continuousTest && _.isEqual( testInfo, data.continuousTest ) ) { + console.log( data.type ); + + // Sent by Joist due to the postMessage* query parameters + if ( data.type === 'continuous-test-wrapper-error' ) { + aqua.simpleFail( data.message + '\n' + data.stack ); + } + else if ( data.type === 'continuous-test-wrapper-unload' ) { + aqua.simpleFail( 'Unloaded frame before complete, window.location probably changed' ); + } + else if ( data.type === 'continuous-test-wrapper-load' ) { + // NOTE: loads may happen more than once, e.g. the mirror wrapper + hasLoaded = true; + } + } +} ); diff --git a/js/pageload-test.js b/js/pageload-test.js deleted file mode 100644 index 1aeb6f7..0000000 --- a/js/pageload-test.js +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2018-2020, University of Colorado Boulder - -/* - * Runs arbitrary content in an iframe (that presumably loads pageload-connector.js) and reports if it successfully - * loads and runs for a short amount of time without erroring - * - * @author Jonathan Olson - */ - -'use strict'; - -const options = QueryStringMachine.getAll( { - url: { - type: 'string', - defaultValue: '' - }, - // If the page doesn't report back by this {number} of milliseconds, then report a failure. - duration: { - type: 'number', - defaultValue: 30000 - } -} ); - -const iframe = document.createElement( 'iframe' ); -iframe.setAttribute( 'frameborder', '0' ); -iframe.setAttribute( 'seamless', '1' ); -iframe.setAttribute( 'width', '512' ); -iframe.setAttribute( 'height', '384' ); -document.body.appendChild( iframe ); - -iframe.src = options.url; - -let hasErrored = false; -let hasLoaded = false; -let durationExpired = false; - -// Our duration timeout. -setTimeout( function() { - if ( !hasLoaded && !hasErrored && !durationExpired ) { - durationExpired = true; - aqua.testFail( 'Did not load in the time allowed: ' + options.duration + 'ms' ); - aqua.nextTest(); - } -}, options.duration ); - -function onPageLoad() { - console.log( 'loaded' ); - if ( !hasLoaded && !hasErrored && !durationExpired ) { - hasLoaded = true; - aqua.testPass(); - aqua.nextTest(); - } -} - -function onPageError( data ) { - console.log( 'error' ); - if ( !hasLoaded && !hasErrored && !durationExpired ) { - hasErrored = true; - - if ( data.message ) { - console.log( 'message:\n' + data.message ); - } - if ( data.stack ) { - console.log( 'stack:\n' + data.stack ); - } - - aqua.testFail( iframe.src + '\n' + data.message + '\n' + data.stack ); - aqua.nextTest(); - } -} - -// handling messages from sims -window.addEventListener( 'message', function( evt ) { - if ( typeof evt.data !== 'string' ) { - return; - } - - const data = JSON.parse( evt.data ); - - // Sent by Joist due to the postMessage* query parameters - if ( data.type === 'pageload-load' ) { - onPageLoad(); - } - else if ( data.type === 'pageload-error' ) { - onPageError( data ); - } -} ); diff --git a/js/server/Test.js b/js/server/Test.js index ce3b6d3..b969cd4 100644 --- a/js/server/Test.js +++ b/js/server/Test.js @@ -19,7 +19,8 @@ const TEST_TYPES = [ 'build', 'sim-test', 'qunit-test', - 'pageload-test' + 'pageload-test', + 'wrapper-test' ]; class Test { @@ -83,7 +84,7 @@ class Test { // @public {string|null} this.url = null; - if ( this.type === 'sim-test' || this.type === 'qunit-test' || this.type === 'pageload-test' ) { + if ( this.type === 'sim-test' || this.type === 'qunit-test' || this.type === 'pageload-test' || this.type === 'wrapper-test' ) { assert( typeof description.url === 'string', `${this.type} tests should have a url` ); this.url = description.url; @@ -161,7 +162,7 @@ class Test { * @returns {boolean} */ isBrowserAvailable( es5Only ) { - if ( this.type !== 'sim-test' && this.type !== 'qunit-test' && this.type !== 'pageload-test' ) { + if ( this.type !== 'sim-test' && this.type !== 'qunit-test' && this.type !== 'pageload-test' && this.type !== 'wrapper-test' ) { return false; } @@ -189,7 +190,7 @@ class Test { * @returns {Object} */ getObjectForBrowser() { - assert( this.type === 'sim-test' || this.type === 'qunit-test' || this.type === 'pageload-test', 'Needs to be a browser test' ); + assert( this.type === 'sim-test' || this.type === 'qunit-test' || this.type === 'pageload-test' || this.type === 'wrapper-test', 'Needs to be a browser test' ); const baseURL = this.snapshot.useRootDir ? '../..' : `../../ct-snapshots/${this.snapshot.timestamp}`; let url; @@ -207,6 +208,9 @@ class Test { else if ( this.type === 'pageload-test' ) { url = 'pageload-test.html?url=' + encodeURIComponent( `${baseURL}/${this.url}` ); } + else if ( this.type === 'wrapper-test' ) { + url = 'wrapper-test.html?url=' + encodeURIComponent( `${baseURL}/${this.url}` ); + } if ( this.testQueryParameters ) { url = url + '&' + this.testQueryParameters; } diff --git a/js/sim-test.js b/js/sim-test.js deleted file mode 100644 index 32d7b7f..0000000 --- a/js/sim-test.js +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2017-2020, University of Colorado Boulder - -/** - * Runs simulation tests in an iframe, and passes results to our parent frame (continuous-loop.html). - * - * @author Jonathan Olson - */ - -'use strict'; - -const options = QueryStringMachine.getAll( { - url: { - type: 'string', - defaultValue: '' - }, - duration: { - type: 'number', - defaultValue: 120000 - }, - simQueryParameters: { - type: 'string', - defaultValue: '' - } -} ); - -const iframe = document.createElement( 'iframe' ); -iframe.setAttribute( 'frameborder', '0' ); -iframe.setAttribute( 'seamless', '1' ); -iframe.setAttribute( 'width', '512' ); -iframe.setAttribute( 'height', '384' ); -document.body.appendChild( iframe ); - -// Add those two to our query parameters, so we get load/error messages -iframe.src = QueryStringMachine.appendQueryStringArray( options.url, - [ '?postMessageOnLoad&postMessageOnError&postMessageOnBeforeUnload', options.simQueryParameters ] ); - -let hasErrored = false; -let hasLoaded = false; -let durationExpired = false; - -// Our duration timeout. -setTimeout( function() { - durationExpired = true; - if ( !hasErrored ) { - if ( hasLoaded ) { - // Only pass the 'run' if it loads AND doesn't error for the entire duration - aqua.testPass(); - } - else { - // If we didn't load, assume it's because of testing load (don't fail for now, but leave in commented bits) - // aqua.testFail( 'Did not load in time allowed: ' + options.duration + 'ms' ); - } - aqua.nextTest(); - } -}, options.duration ); - -function onSimLoad() { - console.log( 'loaded' ); - hasLoaded = true; - - // window.open stub on child. otherwise we get tons of "Report Problem..." popups that stall - iframe.contentWindow.open = function() { - return { - focus: function() {}, - blur: function() {} - }; - }; -} - -function onSimError( data ) { - console.log( 'error' ); - hasErrored = true; - - if ( data.message ) { - console.log( 'message:\n' + data.message ); - } - if ( data.stack ) { - console.log( 'stack:\n' + data.stack ); - } - - const failMessage = iframe.src + '\n' + ( options.simQueryParameters ? ( 'Query: ' + options.simQueryParameters + '\n' ) : '' ) + data.message + '\n' + data.stack; - - aqua.testFail( failMessage ); - aqua.nextTest(); -} - -function onSimUnload() { - console.log( 'unload' ); - - if ( !durationExpired && !hasErrored ) { - hasErrored = true; - console.log( 'Unloaded before duration expired' ); - - aqua.testFail( 'Unloaded frame before complete, window.location probably changed' ); - aqua.nextTest(); - } -} - -// handling messages from sims -window.addEventListener( 'message', function( evt ) { - if ( typeof evt.data !== 'string' ) { - return; - } - const data = JSON.parse( evt.data ); - - // Sent by Joist due to the postMessage* query parameters - if ( data.type === 'load' ) { - onSimLoad(); - } - else if ( data.type === 'error' ) { - onSimError( data ); - } - else if ( data.type === 'beforeUnload' ) { - onSimUnload(); - } -} ); diff --git a/js/test-client.js b/js/test-client.js deleted file mode 100644 index ba4780e..0000000 --- a/js/test-client.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2017-2020, University of Colorado Boulder - -/* - * Common functions used to communicate from test wrappers to continuous-loop.html (assumed to be the parent frame). - * - * @author Jonathan Olson - */ - -'use strict'; - -const aquaOptions = QueryStringMachine.getAll( { - testInfo: { - type: 'string', - defaultValue: '' - } -} ); - -window.aqua = { - /** - * Sends a post message. - * @private - * - * @param {Object} message - */ - sendMessage: function( message ) { - ( window.parent !== window ) && window.parent.postMessage( JSON.stringify( message ), '*' ); - }, - - /** - * Sends a test pass. - * @public - * - * @param {string|undefined} message - */ - testPass: function( message ) { - aqua.sendMessage( { - type: 'test-pass', - message: message, - testInfo: JSON.parse( aquaOptions.testInfo ) - } ); - console.log( '[PASS] ' + message ); - }, - - /** - * Sends a test failure. - * @public - * - * @param {string|undefined} message - */ - testFail: function( message ) { - // Don't send timeouts as failures, since it doesn't usually indicate an underlying problem - if ( message.indexOf( 'errors.html#timeout' ) < 0 ) { - aqua.sendMessage( { - type: 'test-fail', - message: message, - testInfo: JSON.parse( aquaOptions.testInfo ) - } ); - } - console.log( '[FAIL] ' + message ); - }, - - /** - * Sends a request to move to the next test - * @public - */ - nextTest: function() { - aqua.sendMessage( { - type: 'test-next' - } ); - console.log( '[NEXT TEST]' ); - } -}; diff --git a/package.json b/package.json index 80e33f2..95c719e 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,9 @@ } }, "phet": { - "buildStandalone": true + "buildStandalone": true, + "standaloneTranspiles": [ + "../query-string-machine/js/QueryStringMachine.js" + ] } } \ No newline at end of file