Skip to content

Commit

Permalink
Adding node-client prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanolson committed May 4, 2023
1 parent 878a16c commit f4b952e
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 0 deletions.
33 changes: 33 additions & 0 deletions js/node-client/getNextTestInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2023, University of Colorado Boulder

/**
* Returns CT data for the next test to run (for browser-based testing)
*
* @author Jonathan Olson <jonathan.olson@colorado.edu>
*/

const _ = require( 'lodash' );
const axios = require( 'axios' );

/**
* Returns CT data for the next test to run (for browser-based testing)
* @public
*
* @param {Object} [options]
* @returns {Promise.<Object>} - Resolves with data
*/
module.exports = async function( options ) {
options = _.extend( {
server: 'https://sparky.colorado.edu', // {string} - The server to use
old: false // {boolean} - Provided for compatibility/testing, anything using this should be new enough

}, options );

const response = await axios( `${options.server}/aquaserver/next-test?old=${options.old}` );

if ( response.status !== 200 ) {
throw new Error( `nextTest request failed with status ${response.status} ${response}` );
}

return response.data;
};
44 changes: 44 additions & 0 deletions js/node-client/runNextTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2023, University of Colorado Boulder

/**
* Runs the next browser CT test
*
* @author Jonathan Olson <jonathan.olson@colorado.edu>
*/

const getNextTestInfo = require( './getNextTestInfo' );
const runTest = require( './runTest' );
const sendTestResult = require( './sendTestResult' );
const winston = require( 'winston' );

/**
* Runs the next browser CT test
* @public
*
* @param {Object} [options]
* @returns {Promise}
*/
module.exports = async function( options ) {

const testInfo = await getNextTestInfo( options );
winston.info( 'testInfo', testInfo );

const attemptCount = 3;
let attemptsLeft = attemptCount;

let lastFailure;

while ( attemptsLeft-- > 0 ) {
try {
await runTest( testInfo, options );
winston.info( 'runTest completed' );
return;
}
catch( e ) {
lastFailure = e;
}
}

winston.info( 'FAILED TO RUN TEST' );
winston.info( await sendTestResult( `Tried to run ${attemptCount} times, never completed, failure: ${lastFailure}`, testInfo, false, options ) );
};
175 changes: 175 additions & 0 deletions js/node-client/runTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright 2023, University of Colorado Boulder

/**
* Runs a CT test
*
* @author Jonathan Olson <jonathan.olson@colorado.edu>
*/

const _ = require( 'lodash' );
const sendTestResult = require( './sendTestResult' );
const puppeteer = require( 'puppeteer' );
const winston = require( 'winston' );
const sleep = require( '../../../perennial/js/common/sleep' );

/**
* Runs a CT test
* @public
*
* @param {Object} testInfo
* @param {Object} [options]
* @returns {Promise.<Object>} - Resolves with data
*/
module.exports = async function( testInfo, options ) {
options = _.extend( {
server: 'https://sparky.colorado.edu', // {string} - The server to use
browserCreator: puppeteer,
browser: null,

launchOptions: {
args: [
'--disable-gpu'
]
}
}, options );

const majorTimeout = 280000;
const bailTimout = 400000;

const testInfoQueryParam = `testInfo=${encodeURIComponent( JSON.stringify( {
test: testInfo.test,
snapshotName: testInfo.snapshotName,
timestamp: testInfo.timestamp
} ) )}`;

const url = `${options.server}/continuous-testing/aqua/html/${testInfo.url}${testInfo.url.includes( '?' ) ? '&' : '?'}${testInfoQueryParam}`;
winston.info( 'testing url', url );

const ownsBrowser = !options.browser;

let browser;
let page;

try {
browser = options.browser || await options.browserCreator.launch( options.launchOptions );

page = await browser.newPage();
await page.setDefaultNavigationTimeout( majorTimeout );

// TODO: have pendingPassFail when the result isn't sent
let receivedPassFail = false;
let gotNextTest = false;

// promote for use outside the closure
let resolve;
let reject;
const promise = new Promise( ( res, rej ) => {
resolve = res;
reject = rej;
} );

// Define a window.onMessageReceivedEvent function on the page.
await page.exposeFunction( 'onPostMessageReceived', async e => {
// winston.info( 'postMessage', e );

try {
e = JSON.parse( e );
}
catch( e ) {
return;
}

if ( e.type === 'test-pass' ) {
receivedPassFail = true;

winston.info( 'PASS received, sending' );
winston.info( await sendTestResult( e.message, testInfo, true, options ) );
}
else if ( e.type === 'test-fail' ) {
receivedPassFail = false;

winston.info( 'FAIL received, sending' );
winston.info( await sendTestResult( e.message, testInfo, false, options ) );
}
else if ( e.type === 'test-next' ) {
gotNextTest = true;
resolve();
}
} );

await page.evaluateOnNewDocument( () => {
const oldParent = window.parent;

window.parent = {
postMessage: e => {
window.onPostMessageReceived && window.onPostMessageReceived( e );
if ( oldParent ) {
oldParent.postMessage( e );
}
}
};
} );

page.on( 'response', async response => {

// 200 and 300 class status are most likely fine here
if ( response.url() === url && response.status() >= 400 ) {
winston.info( `Could not load from status: ${response.status()}` );
}
} );
page.on( 'console', msg => winston.info( 'console', msg.text() ) );

page.on( 'error', message => {
winston.info( `puppeteer error: ${message}` );
// reject( new Error( message ) );
} );
page.on( 'pageerror', message => {
// if ( options.rejectPageErrors ) {
winston.info( `puppeteer pageerror: ${message}` );
// reject( new Error( message ) );
// }
} );
// page.on( 'frameattached', async frame => {
// winston.info( 'attached', frame.url() );
// } );
// page.on( 'framedetached', async frame => {
// winston.info( 'detached', frame.url() );
// } );
page.on( 'framenavigated', async frame => {
winston.info( 'navigated', frame.url() );
} );

// Run asynchronously
( async () => {
await sleep( bailTimout );
if ( !gotNextTest ) {
if ( receivedPassFail ) {
resolve();
}
else {
reject( new Error( `Did not get next-test message in ${bailTimout}ms` ) );
}
}
} )();
await page.goto( url, {
timeout: majorTimeout
} );
const result = await promise;
winston.info( 'promise resolved' );

!page.isClosed() && await page.close();
winston.info( 'page closed' );

// If we created a temporary browser, close it
ownsBrowser && await browser.close();
winston.info( 'browser closed' );

return result;
}

catch( e ) {
page && !page.isClosed() && await page.close();
ownsBrowser && await browser.close();
throw e;
}
};
40 changes: 40 additions & 0 deletions js/node-client/sendTestResult.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2023, University of Colorado Boulder

/**
* Sends a CT test result
*
* @author Jonathan Olson <jonathan.olson@colorado.edu>
*/

const _ = require( 'lodash' );
const axios = require( 'axios' );
const winston = require( 'winston' );

/**
* Sends a CT test result
* @public
*
* @param {string|undefined} message
* @param {Object} testInfo - The original test request from getNextTestInfo
* @param {boolean} passed
* @param {Object} [options]
* @returns {Promise.<Object>} - Resolves with data
*/
module.exports = async function( message, testInfo, passed, options ) {
options = _.extend( {
server: 'https://sparky.colorado.edu' // {string} - The server to use
}, options );

winston.info( `Sending test result [${passed ? 'PASS' : 'FAIL'}]${message === undefined ? '' : ` with message:\n${message}`}` );

const result = {
passed: passed,
test: testInfo.test,
snapshotName: testInfo.snapshotName,
timestamp: testInfo.timestamp,
message: message,
id: options.id
};

return ( await axios( `${options.server}/aquaserver/test-result?result=${encodeURIComponent( JSON.stringify( result ) )}` ) ).data;
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"devDependencies": {
"@slack/bolt": "~3.11.0",
"axios": "~0.26.1",
"grunt": "~1.5.3",
"lodash": "~4.17.10",
"ncp": "~2.0.0",
Expand Down

0 comments on commit f4b952e

Please sign in to comment.