This makes the tests run agaisnt the deployed production versions (as part of the `aio_monitoring` job) more reliable. PR Close #26649
		
			
				
	
	
		
			123 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			123 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| #!/usr/bin/env node
 | |
| 
 | |
| /**
 | |
|  * Usage:
 | |
|  *   node scripts/test-preview <pr-number> <pr-last-sha> <min-pwa-score>
 | |
|  *
 | |
|  * Checks whether a PR will (eventually) have a (public) preview, waits for the preview to be
 | |
|  * available, and runs PWA tests against the preview.
 | |
|  *
 | |
|  * For PRs that are expected to have a preview, this script will fail if the preview is still not
 | |
|  * available after a pre-defined waiting period or if the PWA tests fail.
 | |
|  */
 | |
| 
 | |
| // Imports
 | |
| const {spawn} = require('child_process');
 | |
| const {get: httpsGet} = require('https');
 | |
| const {relative} = require('path');
 | |
| 
 | |
| // Input
 | |
| const [prNumber, prLastSha, minPwaScore] = validateArgs(process.argv.slice(2));
 | |
| 
 | |
| // Variables
 | |
| const aioBuildsDomain = 'ngbuilds.io';
 | |
| const previewCheckInterval = 30000;
 | |
| const previewCheckAttempts = 10;
 | |
| 
 | |
| const shortSha = prLastSha && prLastSha.slice(0, 7);
 | |
| const previewabilityCheckUrl = `https://${aioBuildsDomain}/can-have-public-preview/${prNumber}`;
 | |
| const previewUrl = `https://pr${prNumber}-${shortSha}.${aioBuildsDomain}/`;
 | |
| 
 | |
| // Check whether the PR can have a (public) preview.
 | |
| get(previewabilityCheckUrl).
 | |
|   then(response => JSON.parse(response)).
 | |
|   then(({canHavePublicPreview, reason}) => {
 | |
|     // Nothing to do, if this PR can have no (public) preview.
 | |
|     if (canHavePublicPreview === false) {
 | |
|       reportNoPreview(reason);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // There should be a preview. Wait for it to be available.
 | |
|     return poll(previewCheckInterval, previewCheckAttempts, () => get(previewUrl)).
 | |
|       // The preview is still not available after the specified waiting period.
 | |
|       catch(() => {
 | |
|         const totalSecs = Math.round((previewCheckInterval * previewCheckAttempts) / 1000);
 | |
|         throw new Error(`Preview still not available after ${totalSecs}s.`);
 | |
|       }).
 | |
|       // The preview is now available. Run the tests.
 | |
|       then(() => yarnRun('smoke-tests', previewUrl)).
 | |
|       then(() => yarnRun('test-pwa-score', previewUrl, minPwaScore));
 | |
|   }).
 | |
|   catch(onError);
 | |
| 
 | |
| // Helpers
 | |
| function get(url) {
 | |
|   console.log(`GET ${url}`);
 | |
|   return new Promise((resolve, reject) => {
 | |
|     const onResponse = res => {
 | |
|       const statusCode = res.statusCode || -1;
 | |
|       const isSuccess = (200 <= statusCode) && (statusCode < 400);
 | |
|       let responseText = '';
 | |
| 
 | |
|       res.
 | |
|         on('error', reject).
 | |
|         on('data', d => responseText += d).
 | |
|         on('end', () => isSuccess ?
 | |
|           resolve(responseText) :
 | |
|           reject(`Request to '${url}' failed (status: ${statusCode}): ${responseText}`));
 | |
|     };
 | |
| 
 | |
|     httpsGet(url, onResponse).
 | |
|       on('error', reject);
 | |
|   });
 | |
| }
 | |
| 
 | |
| function onError(err) {
 | |
|   console.error(err);
 | |
|   process.exit(1);
 | |
| }
 | |
| 
 | |
| function poll(interval, attempts, checkCondition) {
 | |
|   return new Promise((resolve, reject) => {
 | |
|     if (!attempts) return reject();
 | |
| 
 | |
|     checkCondition().
 | |
|       then(() => resolve()).
 | |
|       catch(() => wait(interval).
 | |
|         then(() => poll(interval, attempts - 1, checkCondition)).
 | |
|         then(resolve, reject));
 | |
|   });
 | |
| }
 | |
| 
 | |
| function reportNoPreview(reason) {
 | |
|   console.log(`No (public) preview available. (Reason: ${reason})`);
 | |
| }
 | |
| 
 | |
| function validateArgs(args) {
 | |
|   if (args.length !== 3) {
 | |
|     const relativeScriptPath = relative('.', __filename.replace(/\.js$/, ''));
 | |
|     const usageCmd = `node ${relativeScriptPath} <pr-number> <pr-last-sha> <min-pwa-score>`;
 | |
| 
 | |
|     return onError(
 | |
|       `Invalid number of arguments (expected 3, found ${args.length}).\n` +
 | |
|       `Usage: ${usageCmd}`);
 | |
|   }
 | |
| 
 | |
|   return args;
 | |
| }
 | |
| 
 | |
| function wait(delay) {
 | |
|   console.log(`Waiting ${delay}ms...`);
 | |
|   return new Promise(resolve => setTimeout(resolve, delay));
 | |
| }
 | |
| 
 | |
| function yarnRun(script, ...args) {
 | |
|   return new Promise((resolve, reject) => {
 | |
|     const spawnOptions = {cwd: __dirname, stdio: 'inherit'};
 | |
|     spawn('yarn', [script, ...args], spawnOptions).
 | |
|       on('error', reject).
 | |
|       on('exit', code => (code === 0 ? resolve : reject)());
 | |
|   });
 | |
| }
 |