@@ -9,15 +9,16 @@ import {
99 IntegrationState ,
1010 UploadReleaseResult ,
1111 TestDevice ,
12+ ReleaseTest ,
1213} from "../appdistribution/types" ;
1314import { FirebaseError , getErrMsg , getErrStatus } from "../error" ;
1415import { Distribution , DistributionFileType } from "../appdistribution/distribution" ;
1516import {
1617 ensureFileExists ,
1718 getAppName ,
1819 getLoginCredential ,
19- getTestDevices ,
20- getTestersOrGroups ,
20+ parseTestDevices ,
21+ parseIntoStringArray ,
2122} from "../appdistribution/options-parser-util" ;
2223
2324const TEST_MAX_POLLING_RETRIES = 40 ;
@@ -35,19 +36,21 @@ function getReleaseNotes(releaseNotes: string, releaseNotesFile: string): string
3536}
3637
3738export const command = new Command ( "appdistribution:distribute <release-binary-file>" )
38- . description ( "upload a release binary" )
39+ . description (
40+ "upload a release binary and optionally distribute it to testers and run automated tests" ,
41+ )
3942 . option ( "--app <app_id>" , "the app id of your Firebase app" )
4043 . option ( "--release-notes <string>" , "release notes to include" )
4144 . option ( "--release-notes-file <file>" , "path to file with release notes" )
42- . option ( "--testers <string>" , "a comma separated list of tester emails to distribute to" )
45+ . option ( "--testers <string>" , "a comma- separated list of tester emails to distribute to" )
4346 . option (
4447 "--testers-file <file>" ,
45- "path to file with a comma separated list of tester emails to distribute to" ,
48+ "path to file with a comma- or newline- separated list of tester emails to distribute to" ,
4649 )
47- . option ( "--groups <string>" , "a comma separated list of group aliases to distribute to" )
50+ . option ( "--groups <string>" , "a comma- separated list of group aliases to distribute to" )
4851 . option (
4952 "--groups-file <file>" ,
50- "path to file with a comma separated list of group aliases to distribute to" ,
53+ "path to file with a comma- or newline- separated list of group aliases to distribute to" ,
5154 )
5255 . option (
5356 "--test-devices <string>" ,
@@ -75,14 +78,25 @@ export const command = new Command("appdistribution:distribute <release-binary-f
7578 "--test-non-blocking" ,
7679 "run automated tests without waiting for them to complete. Visit the Firebase console for the test results." ,
7780 )
81+ . option ( "--test-case-ids <string>" , "a comma-separated list of test case IDs." )
82+ . option (
83+ "--test-case-ids-file <file>" ,
84+ "path to file with a comma- or newline-separated list of test case IDs." ,
85+ )
7886 . before ( requireAuth )
7987 . action ( async ( file : string , options : any ) => {
8088 const appName = getAppName ( options ) ;
8189 const distribution = new Distribution ( file ) ;
8290 const releaseNotes = getReleaseNotes ( options . releaseNotes , options . releaseNotesFile ) ;
83- const testers = getTestersOrGroups ( options . testers , options . testersFile ) ;
84- const groups = getTestersOrGroups ( options . groups , options . groupsFile ) ;
85- const testDevices = getTestDevices ( options . testDevices , options . testDevicesFile ) ;
91+ const testers = parseIntoStringArray ( options . testers , options . testersFile ) ;
92+ const groups = parseIntoStringArray ( options . groups , options . groupsFile ) ;
93+ const testCases = parseIntoStringArray ( options . testCaseIds , options . testCaseIdsFile ) ;
94+ const testDevices = parseTestDevices ( options . testDevices , options . testDevicesFile ) ;
95+ if ( testCases . length && ( options . testUsernameResource || options . testPasswordResource ) ) {
96+ throw new FirebaseError (
97+ "Password and username resource names are not supported for the AI testing agent." ,
98+ ) ;
99+ }
86100 const loginCredential = getLoginCredential ( {
87101 username : options . testUsername ,
88102 password : options . testPassword ,
@@ -210,56 +224,78 @@ export const command = new Command("appdistribution:distribute <release-binary-f
210224 await requests . distribute ( releaseName , testers , groups ) ;
211225
212226 // Run automated tests
213- if ( testDevices ?. length ) {
214- utils . logBullet ( "starting automated tests (note: this feature is in beta)" ) ;
215- const releaseTest = await requests . createReleaseTest (
216- releaseName ,
217- testDevices ,
218- loginCredential ,
219- ) ;
220- utils . logSuccess ( `Release test created successfully` ) ;
227+ if ( testDevices . length ) {
228+ utils . logBullet ( "starting automated test (note: this feature is in beta)" ) ;
229+ const releaseTestPromises : Promise < ReleaseTest > [ ] = [ ] ;
230+ if ( ! testCases . length ) {
231+ // fallback to basic automated test
232+ releaseTestPromises . push (
233+ requests . createReleaseTest ( releaseName , testDevices , loginCredential ) ,
234+ ) ;
235+ } else {
236+ for ( const testCaseId of testCases ) {
237+ releaseTestPromises . push (
238+ requests . createReleaseTest (
239+ releaseName ,
240+ testDevices ,
241+ loginCredential ,
242+ `${ appName } /testCases/${ testCaseId } ` ,
243+ ) ,
244+ ) ;
245+ }
246+ }
247+ const releaseTests = await Promise . all ( releaseTestPromises ) ;
248+ utils . logSuccess ( `${ releaseTests . length } release test(s) started successfully` ) ;
221249 if ( ! options . testNonBlocking ) {
222- await awaitTestResults ( releaseTest . name ! , requests ) ;
250+ await awaitTestResults ( releaseTests , requests ) ;
223251 }
224252 }
225253 } ) ;
226254
227255async function awaitTestResults (
228- releaseTestName : string ,
256+ releaseTests : ReleaseTest [ ] ,
229257 requests : AppDistributionClient ,
230258) : Promise < void > {
259+ const releaseTestNames = new Set ( releaseTests . map ( ( rt ) => rt . name ! ) ) ;
231260 for ( let i = 0 ; i < TEST_MAX_POLLING_RETRIES ; i ++ ) {
232- utils . logBullet ( "the automated tests results are pending" ) ;
261+ utils . logBullet ( ` ${ releaseTestNames . size } automated test results are pending...` ) ;
233262 await delay ( TEST_POLLING_INTERVAL_MILLIS ) ;
234- const releaseTest = await requests . getReleaseTest ( releaseTestName ) ;
235- if ( releaseTest . deviceExecutions . every ( ( e ) => e . state === "PASSED" ) ) {
236- utils . logSuccess ( "automated test(s) passed!" ) ;
237- return ;
238- }
239- for ( const execution of releaseTest . deviceExecutions ) {
240- switch ( execution . state ) {
241- case "PASSED" :
242- case "IN_PROGRESS" :
263+ for ( const releaseTestName of releaseTestNames ) {
264+ const releaseTest = await requests . getReleaseTest ( releaseTestName ) ;
265+ if ( releaseTest . deviceExecutions . every ( ( e ) => e . state === "PASSED" ) ) {
266+ releaseTestNames . delete ( releaseTestName ) ;
267+ if ( releaseTestNames . size === 0 ) {
268+ utils . logSuccess ( "Automated test(s) passed!" ) ;
269+ return ;
270+ } else {
243271 continue ;
244- case "FAILED" :
245- throw new FirebaseError (
246- `Automated test failed for ${ deviceToString ( execution . device ) } : ${ execution . failedReason } ` ,
247- { exit : 1 } ,
248- ) ;
249- case "INCONCLUSIVE" :
250- throw new FirebaseError (
251- `Automated test inconclusive for ${ deviceToString ( execution . device ) } : ${ execution . inconclusiveReason } ` ,
252- { exit : 1 } ,
253- ) ;
254- default :
255- throw new FirebaseError (
256- `Unsupported automated test state for ${ deviceToString ( execution . device ) } : ${ execution . state } ` ,
257- { exit : 1 } ,
258- ) ;
272+ }
273+ }
274+ for ( const execution of releaseTest . deviceExecutions ) {
275+ switch ( execution . state ) {
276+ case "PASSED" :
277+ case "IN_PROGRESS" :
278+ continue ;
279+ case "FAILED" :
280+ throw new FirebaseError (
281+ `Automated test failed for ${ deviceToString ( execution . device ) } : ${ execution . failedReason } ` ,
282+ { exit : 1 } ,
283+ ) ;
284+ case "INCONCLUSIVE" :
285+ throw new FirebaseError (
286+ `Automated test inconclusive for ${ deviceToString ( execution . device ) } : ${ execution . inconclusiveReason } ` ,
287+ { exit : 1 } ,
288+ ) ;
289+ default :
290+ throw new FirebaseError (
291+ `Unsupported automated test state for ${ deviceToString ( execution . device ) } : ${ execution . state } ` ,
292+ { exit : 1 } ,
293+ ) ;
294+ }
259295 }
260296 }
261297 }
262- throw new FirebaseError ( "It took longer than expected to process your test, please try again." , {
298+ throw new FirebaseError ( "It took longer than expected to run your test(s) , please try again." , {
263299 exit : 1 ,
264300 } ) ;
265301}
0 commit comments