@@ -6,18 +6,19 @@ import * as clc from "colorette";
66import { parseTestFiles } from "../apptesting/parseTestFiles" ;
77import * as ora from "ora" ;
88import { invokeTests , pollInvocationStatus } from "../apptesting/invokeTests" ;
9- import { TestInvocation } from "../apptesting/types" ;
9+ import { ExecutionMetadata } from "../apptesting/types" ;
1010import { FirebaseError } from "../error" ;
1111import { marked } from "marked" ;
1212import { needProjectId } from "../projectUtils" ;
1313import { consoleUrl } from "../utils" ;
1414import { AppPlatform , listFirebaseApps } from "../management/apps" ;
1515
1616export const command = new Command ( "apptesting:execute <target>" )
17- . description (
18- "upload a release binary and optionally distribute it to testers and run automated tests" ,
17+ . description ( "Run automated tests written in natural language driven by AI" )
18+ . option (
19+ "--app <app_id>" ,
20+ "The app id of your Firebase web app. Optional if the project contains exactly one web app." ,
1921 )
20- . option ( "--app <app_id>" , "the app id of your Firebase app" )
2122 . option (
2223 "--test-file-pattern <pattern>" ,
2324 "Test file pattern. Only tests contained in files that match this pattern will be executed." ,
@@ -30,15 +31,13 @@ export const command = new Command("apptesting:execute <target>")
3031 . before ( requireAuth )
3132 . before ( requireConfig )
3233 . action ( async ( target : string , options : any ) => {
33- if ( ! options . app ) {
34- throw new FirebaseError ( "App is required" ) ;
35- }
36-
3734 const projectId = needProjectId ( options ) ;
3835 const appList = await listFirebaseApps ( projectId , AppPlatform . WEB ) ;
39- const app = appList . find ( ( a ) => a . appId === options . app ) ;
40-
41- if ( ! app ) {
36+ let app = appList . find ( ( a ) => a . appId === options . app ) ;
37+ if ( ! app && appList . length === 1 ) {
38+ app = appList [ 0 ] ;
39+ logger . info ( `No app specified, defaulting to ${ app . appId } ` ) ;
40+ } else {
4241 throw new FirebaseError ( "Invalid app id" ) ;
4342 }
4443
@@ -54,13 +53,20 @@ export const command = new Command("apptesting:execute <target>")
5453 throw new FirebaseError ( "No tests found" ) ;
5554 }
5655
57- logger . info ( clc . bold ( `\n${ clc . white ( "===" ) } Running ${ tests . length } tests` ) ) ;
58-
59- const invokeSpinner = ora ( "Sending test request" ) ;
56+ const invokeSpinner = ora ( "Requesting test execution" ) ;
6057 invokeSpinner . start ( ) ;
61- const invocationOperation = await invokeTests ( options . app , target , tests ) ;
62- invokeSpinner . text = "Testing started" ;
63- invokeSpinner . succeed ( ) ;
58+
59+ let invocationOperation ;
60+ try {
61+ invocationOperation = await invokeTests ( app . appId , target , tests ) ;
62+ invokeSpinner . text = "Test execution requested" ;
63+ invokeSpinner . succeed ( ) ;
64+ } catch ( ex ) {
65+ invokeSpinner . fail ( "Failed to request test execution" ) ;
66+ throw ex ;
67+ }
68+
69+ logger . info ( clc . bold ( `\n${ clc . white ( "===" ) } Running ${ pluralizeTests ( tests . length ) } ` ) ) ;
6470
6571 const invocationId = invocationOperation . name ?. split ( "/" ) . pop ( ) ;
6672
@@ -84,29 +90,40 @@ export const command = new Command("apptesting:execute <target>")
8490
8591 const executionSpinner = ora ( getOutput ( invocationOperation . metadata ) ) ;
8692 executionSpinner . start ( ) ;
87- await pollInvocationStatus ( invocationOperation . name , ( operation ) => {
88- if ( ! operation . response ) {
89- logger . info ( "invocation details unavailable" ) ;
90- return ;
93+ const invocationOp = await pollInvocationStatus ( invocationOperation . name , ( operation ) => {
94+ if ( ! operation . done ) {
95+ executionSpinner . text = getOutput ( operation . metadata as ExecutionMetadata ) ;
9196 }
92- executionSpinner . text = getOutput ( operation . metadata as TestInvocation ) ;
9397 } ) ;
94- executionSpinner . succeed ( ) ;
98+ const response = invocationOp . resource . testInvocation ;
99+ executionSpinner . text = `Testing complete\n${ getOutput ( response ) } ` ;
100+ if ( response . failedExecutions || response . cancelledExecutions ) {
101+ executionSpinner . fail ( ) ;
102+ throw new FirebaseError ( "Testing complete with errors" ) ;
103+ } else {
104+ executionSpinner . succeed ( ) ;
105+ }
95106 } ) ;
96107
97- function getOutput ( invocation : TestInvocation ) {
98- if ( ! invocation . failedExecutions && ! invocation . runningExecutions ) {
99- return "All tests passed" ;
108+ function pluralizeTests ( numTests : number ) {
109+ return `${ numTests } test${ numTests === 1 ? "" : "s" } ` ;
110+ }
111+
112+ function getOutput ( invocation : ExecutionMetadata ) {
113+ const output = [ ] ;
114+ if ( invocation . runningExecutions ) {
115+ output . push (
116+ `${ pluralizeTests ( invocation . runningExecutions ) } running (this may take a while)...` ,
117+ ) ;
118+ }
119+ if ( invocation . succeededExecutions ) {
120+ output . push ( `✔ ${ pluralizeTests ( invocation . succeededExecutions ) } passed` ) ;
121+ }
122+ if ( invocation . failedExecutions ) {
123+ output . push ( `✖ ${ pluralizeTests ( invocation . failedExecutions ) } failed` ) ;
124+ }
125+ if ( invocation . cancelledExecutions ) {
126+ output . push ( `⊝ ${ pluralizeTests ( invocation . cancelledExecutions ) } cancelled` ) ;
100127 }
101- return [
102- invocation . runningExecutions
103- ? `${ invocation . runningExecutions } tests still running (this may take a while)...`
104- : undefined ,
105- invocation . succeededExecutions
106- ? `✔ ${ invocation . succeededExecutions } tests passing`
107- : undefined ,
108- invocation . failedExecutions ? `✖ ${ invocation . failedExecutions } tests failing` : undefined ,
109- ]
110- . filter ( ( a ) => a )
111- . join ( "\n" ) ;
128+ return output . length ? output . join ( "\n" ) : "Tests are starting" ;
112129}
0 commit comments