2017-07-28 08:09:26 +00:00
/ * *
* Copyright 2017 Google Inc . All rights reserved .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2018-08-09 23:51:12 +00:00
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
2019-08-01 05:23:50 +00:00
const { FlakinessDashboard } = require ( '../utils/flakiness-dashboard' ) ;
2018-08-09 23:51:12 +00:00
const PROJECT _ROOT = fs . existsSync ( path . join ( _ _dirname , '..' , 'package.json' ) ) ? path . join ( _ _dirname , '..' ) : path . join ( _ _dirname , '..' , '..' ) ;
2019-01-26 04:21:14 +00:00
/ * *
* @ param { Map < string , boolean > } apiCoverage
* @ param { string } className
* @ param { ! Object } classType
* /
function traceAPICoverage ( apiCoverage , className , classType ) {
className = className . substring ( 0 , 1 ) . toLowerCase ( ) + className . substring ( 1 ) ;
for ( const methodName of Reflect . ownKeys ( classType . prototype ) ) {
const method = Reflect . get ( classType . prototype , methodName ) ;
if ( methodName === 'constructor' || typeof methodName !== 'string' || methodName . startsWith ( '_' ) || typeof method !== 'function' )
continue ;
apiCoverage . set ( ` ${ className } . ${ methodName } ` , false ) ;
Reflect . set ( classType . prototype , methodName , function ( ... args ) {
apiCoverage . set ( ` ${ className } . ${ methodName } ` , true ) ;
return method . call ( this , ... args ) ;
} ) ;
}
if ( classType . Events ) {
for ( const event of Object . values ( classType . Events ) )
apiCoverage . set ( ` ${ className } .emit( ${ JSON . stringify ( event ) } ) ` , false ) ;
const method = Reflect . get ( classType . prototype , 'emit' ) ;
Reflect . set ( classType . prototype , 'emit' , function ( event , ... args ) {
if ( this . listenerCount ( event ) )
apiCoverage . set ( ` ${ className } .emit( ${ JSON . stringify ( event ) } ) ` , true ) ;
return method . call ( this , event , ... args ) ;
} ) ;
}
}
2017-06-22 20:38:10 +00:00
const utils = module . exports = {
2019-01-26 04:21:14 +00:00
recordAPICoverage : function ( testRunner , api , disabled ) {
const coverage = new Map ( ) ;
for ( const [ className , classType ] of Object . entries ( api ) )
traceAPICoverage ( coverage , className , classType ) ;
testRunner . describe ( 'COVERAGE' , ( ) => {
for ( const method of coverage . keys ( ) ) {
( disabled . has ( method ) ? testRunner . xit : testRunner . it ) ( ` public api ' ${ method } ' should be called ` , async ( { page , server } ) => {
if ( ! coverage . get ( method ) )
throw new Error ( 'NOT CALLED!' ) ;
} ) ;
}
} ) ;
} ,
2018-08-09 23:51:12 +00:00
/ * *
* @ return { string }
* /
projectRoot : function ( ) {
return PROJECT _ROOT ;
} ,
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Page } page
* @ param { string } frameId
* @ param { string } url
2018-09-20 18:31:19 +00:00
* @ return { ! Puppeteer . Frame }
2017-06-21 20:58:49 +00:00
* /
2017-06-21 20:51:06 +00:00
attachFrame : async function ( page , frameId , url ) {
2018-09-20 18:31:19 +00:00
const handle = await page . evaluateHandle ( attachFrame , frameId , url ) ;
return await handle . asElement ( ) . contentFrame ( ) ;
2017-06-17 21:27:51 +00:00
2018-09-20 18:31:19 +00:00
async function attachFrame ( frameId , url ) {
2017-08-21 23:39:04 +00:00
const frame = document . createElement ( 'iframe' ) ;
2017-06-21 20:51:06 +00:00
frame . src = url ;
frame . id = frameId ;
document . body . appendChild ( frame ) ;
2018-09-20 18:31:19 +00:00
await new Promise ( x => frame . onload = x ) ;
return frame ;
2017-06-21 20:51:06 +00:00
}
} ,
2017-06-17 21:27:51 +00:00
2019-02-13 03:10:14 +00:00
isFavicon : function ( request ) {
return request . url ( ) . includes ( 'favicon.ico' ) ;
} ,
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Page } page
* @ param { string } frameId
* /
2017-06-21 20:51:06 +00:00
detachFrame : async function ( page , frameId ) {
await page . evaluate ( detachFrame , frameId ) ;
2017-06-17 21:27:51 +00:00
2017-06-21 20:51:06 +00:00
function detachFrame ( frameId ) {
2017-08-21 23:39:04 +00:00
const frame = document . getElementById ( frameId ) ;
2017-06-21 20:51:06 +00:00
frame . remove ( ) ;
}
} ,
2017-06-17 21:27:51 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Page } page
* @ param { string } frameId
* @ param { string } url
* /
2017-06-21 20:51:06 +00:00
navigateFrame : async function ( page , frameId , url ) {
await page . evaluate ( navigateFrame , frameId , url ) ;
2017-06-17 21:27:51 +00:00
2017-06-21 20:51:06 +00:00
function navigateFrame ( frameId , url ) {
2017-08-21 23:39:04 +00:00
const frame = document . getElementById ( frameId ) ;
2017-06-21 20:51:06 +00:00
frame . src = url ;
return new Promise ( x => frame . onload = x ) ;
}
} ,
2017-06-17 21:27:51 +00:00
2017-06-21 20:51:06 +00:00
/ * *
2017-06-21 20:58:49 +00:00
* @ param { ! Frame } frame
* @ param { string = } indentation
2019-02-06 21:49:14 +00:00
* @ return { Array < string > }
2017-06-21 20:58:49 +00:00
* /
2017-06-21 20:51:06 +00:00
dumpFrames : function ( frame , indentation ) {
indentation = indentation || '' ;
2019-02-06 21:49:14 +00:00
let description = frame . url ( ) . replace ( /:\d{4}\// , ':<PORT>/' ) ;
if ( frame . name ( ) )
description += ' (' + frame . name ( ) + ')' ;
const result = [ indentation + description ] ;
2017-08-21 23:39:04 +00:00
for ( const child of frame . childFrames ( ) )
2019-02-06 21:49:14 +00:00
result . push ( ... utils . dumpFrames ( child , ' ' + indentation ) ) ;
2017-06-21 20:51:06 +00:00
return result ;
} ,
2018-03-20 03:00:12 +00:00
/ * *
* @ param { ! EventEmitter } emitter
* @ param { string } eventName
* @ return { ! Promise < ! Object > }
* /
2018-08-09 21:57:08 +00:00
waitEvent : function ( emitter , eventName , predicate = ( ) => true ) {
return new Promise ( fulfill => {
emitter . on ( eventName , function listener ( event ) {
if ( ! predicate ( event ) )
return ;
emitter . removeListener ( eventName , listener ) ;
fulfill ( event ) ;
} ) ;
} ) ;
2018-03-20 03:00:12 +00:00
} ,
2019-08-01 05:23:50 +00:00
initializeFlakinessDashboardIfNeeded : function ( testRunner ) {
// Generate testIDs for all tests and verify they don't clash.
// This will add |test.testId| for every test.
//
// NOTE: we do this unconditionally so that developers can see problems in
// their local setups.
generateTestIDs ( testRunner ) ;
// FLAKINESS_DASHBOARD_PASSWORD is an encrypted/secured variable.
// Encrypted variables get a special treatment in CI's when handling PRs so that
// secrets are not leaked to untrusted code.
// - AppVeyor DOES NOT decrypt secured variables for PRs
// - Travis DOES NOT decrypt encrypted variables for PRs
// - Cirrus CI DOES NOT decrypt encrypted variables for PRs *unless* PR is sent
// from someone who has WRITE ACCESS to the repo.
//
// Since we don't want to run flakiness dashboard for PRs on all CIs, we
// check existance of FLAKINESS_DASHBOARD_PASSWORD and absense of
// CIRRUS_BASE_SHA env variables.
if ( ! process . env . FLAKINESS _DASHBOARD _PASSWORD || process . env . CIRRUS _BASE _SHA )
return ;
const sha = process . env . FLAKINESS _DASHBOARD _BUILD _SHA ;
const dashboard = new FlakinessDashboard ( {
dashboardName : process . env . FLAKINESS _DASHBOARD _NAME ,
build : {
url : process . env . FLAKINESS _DASHBOARD _BUILD _URL ,
name : sha . substring ( 0 , 8 ) ,
} ,
dashboardRepo : {
url : 'https://github.com/aslushnikov/puppeteer-flakiness-dashboard.git' ,
username : 'puppeteer-flakiness' ,
email : 'aslushnikov+puppeteerflakiness@gmail.com' ,
password : process . env . FLAKINESS _DASHBOARD _PASSWORD ,
} ,
} ) ;
testRunner . on ( 'testfinished' , test => {
const testpath = test . location . filePath . substring ( utils . projectRoot ( ) . length ) ;
const url = ` https://github.com/GoogleChrome/puppeteer/blob/ ${ sha } / ${ testpath } #L ${ test . location . lineNumber } ` ;
dashboard . reportTestResult ( {
testId : test . testId ,
name : test . location . fileName + ':' + test . location . lineNumber ,
description : test . fullName ,
url ,
result : test . result ,
} ) ;
} ) ;
testRunner . on ( 'terminated' , ( ) => dashboard . uploadAndCleanup ( ) ) ;
testRunner . on ( 'finished' , ( ) => dashboard . uploadAndCleanup ( ) ) ;
function generateTestIDs ( testRunner ) {
const testIds = new Map ( ) ;
for ( const test of testRunner . tests ( ) ) {
const testIdComponents = [ test . name ] ;
for ( let suite = test . suite ; ! ! suite . parentSuite ; suite = suite . parentSuite )
testIdComponents . push ( suite . name ) ;
testIdComponents . reverse ( ) ;
const testId = testIdComponents . join ( '>' ) ;
const clashingTest = testIds . get ( testId ) ;
if ( clashingTest )
throw new Error ( ` Two tests with clashing IDs: ${ test . location . fileName } : ${ test . location . lineNumber } and ${ clashingTest . location . fileName } : ${ clashingTest . location . lineNumber } ` ) ;
testIds . set ( testId , test ) ;
test . testId = testId ;
}
}
} ,
2017-06-17 21:27:51 +00:00
} ;