11import { dirExistsSync , fileExistsSync , listFiles } from "../fsutils" ;
22import { join } from "path" ;
33import { logger } from "../logger" ;
4- import { Browser , TestCaseInvocation } from "./types" ;
4+ import { Browser , TestCaseInvocation , TestStep } from "./types" ;
55import { readFileFromDirectory , wrappedSafeLoad } from "../utils" ;
66import { FirebaseError , getErrMsg , getError } from "../error" ;
77
8- function createFilter ( pattern ?: string ) {
9- const regex = pattern ? new RegExp ( pattern ) : undefined ;
10- return ( s : string ) => ! regex || regex . test ( s ) ;
11- }
12-
138export async function parseTestFiles (
149 dir : string ,
1510 targetUri ?: string ,
@@ -26,52 +21,129 @@ export async function parseTestFiles(
2621 }
2722 }
2823
24+ const files = await parseTestFilesRecursive ( { testDir : dir , targetUri } ) ;
25+ const idToInvocation = files
26+ . flatMap ( ( file ) => file . invocations )
27+ . reduce (
28+ ( accumulator , invocation ) => {
29+ if ( invocation . testCase . id ) {
30+ accumulator [ invocation . testCase . id ] = invocation ;
31+ }
32+ return accumulator ;
33+ } ,
34+ { } as Record < string , TestCaseInvocation > ,
35+ ) ;
36+
2937 const fileFilterFn = createFilter ( filePattern ) ;
3038 const nameFilterFn = createFilter ( namePattern ) ;
39+ const filteredInvocations = files
40+ . filter ( ( file ) => fileFilterFn ( file . path ) )
41+ . flatMap ( ( file ) => file . invocations )
42+ . filter ( ( invocation ) => nameFilterFn ( invocation . testCase . displayName ) ) ;
43+
44+ return filteredInvocations . map ( ( invocation ) => {
45+ let prerequisiteTestCaseId = invocation . testCase . prerequisiteTestCaseId ;
46+ if ( prerequisiteTestCaseId === undefined ) {
47+ return invocation ;
48+ }
3149
32- async function parseTestFilesRecursive ( testDir : string ) : Promise < TestCaseInvocation [ ] > {
33- const items = listFiles ( testDir ) ;
34- const results = [ ] ;
35- for ( const item of items ) {
36- const path = join ( testDir , item ) ;
37- if ( dirExistsSync ( path ) ) {
38- results . push ( ...( await parseTestFilesRecursive ( path ) ) ) ;
39- } else if ( fileFilterFn ( path ) && fileExistsSync ( path ) ) {
40- try {
41- const file = await readFileFromDirectory ( testDir , item ) ;
42- const parsedFile = wrappedSafeLoad ( file . source ) ;
43- const tests = parsedFile . tests ;
44- const defaultConfig = parsedFile . defaultConfig ;
45- if ( ! tests || ! tests . length ) {
46- logger . info ( `No tests found in ${ path } . Ignoring.` ) ;
47- continue ;
48- }
49- for ( const rawTestDef of parsedFile . tests ) {
50- if ( ! nameFilterFn ( rawTestDef . testName ) ) continue ;
51- const testDef = toTestDef ( rawTestDef , defaultConfig , targetUri ) ;
52- results . push ( testDef ) ;
53- }
54- } catch ( ex ) {
55- const errMsg = getErrMsg ( ex ) ;
56- const errDetails = errMsg ? `Error details: \n${ errMsg } ` : "" ;
57- logger . info ( `Unable to parse test file ${ path } . Ignoring.${ errDetails } ` ) ;
50+ const prerequisiteSteps : TestStep [ ] = [ ] ;
51+ const previousTestCaseIds = new Set < string > ( ) ;
52+ while ( prerequisiteTestCaseId ) {
53+ if ( previousTestCaseIds . has ( prerequisiteTestCaseId ) ) {
54+ throw new FirebaseError ( `Detected a cycle in prerequisite test cases.` ) ;
55+ }
56+ previousTestCaseIds . add ( prerequisiteTestCaseId ) ;
57+ const prerequisiteTestCaseInvocation : TestCaseInvocation | undefined =
58+ idToInvocation [ prerequisiteTestCaseId ] ;
59+ if ( prerequisiteTestCaseInvocation === undefined ) {
60+ throw new FirebaseError (
61+ `Invalid prerequisiteTestCaseId. There is no test case with id ${ prerequisiteTestCaseId } ` ,
62+ ) ;
63+ }
64+ prerequisiteSteps . unshift ( ...prerequisiteTestCaseInvocation . testCase . instructions . steps ) ;
65+ prerequisiteTestCaseId = prerequisiteTestCaseInvocation . testCase . prerequisiteTestCaseId ;
66+ }
67+
68+ return {
69+ ...invocation ,
70+ testCase : {
71+ ...invocation . testCase ,
72+ instructions : {
73+ ...invocation . testCase . instructions ,
74+ steps : prerequisiteSteps . concat ( invocation . testCase . instructions . steps ) ,
75+ } ,
76+ } ,
77+ } ;
78+ } ) ;
79+ }
80+
81+ function createFilter ( pattern ?: string ) {
82+ const regex = pattern ? new RegExp ( pattern ) : undefined ;
83+ return ( s : string ) => ! regex || regex . test ( s ) ;
84+ }
85+
86+ interface TestCaseFile {
87+ path : string ;
88+ invocations : TestCaseInvocation [ ] ;
89+ }
90+
91+ async function parseTestFilesRecursive ( params : {
92+ testDir : string ;
93+ targetUri ?: string ;
94+ } ) : Promise < TestCaseFile [ ] > {
95+ const testDir = params . testDir ;
96+ const targetUri = params . targetUri ;
97+ const items = listFiles ( testDir ) ;
98+ const results = [ ] ;
99+ for ( const item of items ) {
100+ const path = join ( testDir , item ) ;
101+ if ( dirExistsSync ( path ) ) {
102+ results . push ( ...( await parseTestFilesRecursive ( { testDir : path , targetUri } ) ) ) ;
103+ } else if ( fileExistsSync ( path ) ) {
104+ try {
105+ const file = await readFileFromDirectory ( testDir , item ) ;
106+ logger . debug ( `Read the file ${ file . source } .` ) ;
107+ const parsedFile = wrappedSafeLoad ( file . source ) ;
108+ logger . debug ( `Parsed the file.` ) ;
109+ const tests = parsedFile . tests ;
110+ logger . debug ( `There are ${ tests . length } tests.` ) ;
111+ const defaultConfig = parsedFile . defaultConfig ;
112+ if ( ! tests || ! tests . length ) {
113+ logger . debug ( `No tests found in ${ path } . Ignoring.` ) ;
58114 continue ;
59115 }
116+ const invocations = [ ] ;
117+ for ( const rawTestDef of tests ) {
118+ const invocation = toTestCaseInvocation ( rawTestDef , targetUri , defaultConfig ) ;
119+ invocations . push ( invocation ) ;
120+ }
121+ results . push ( { path, invocations : invocations } ) ;
122+ } catch ( ex ) {
123+ const errMsg = getErrMsg ( ex ) ;
124+ const errDetails = errMsg ? `Error details: \n${ errMsg } ` : "" ;
125+ logger . debug ( `Unable to parse test file ${ path } . Ignoring.${ errDetails } ` ) ;
126+ continue ;
60127 }
61128 }
62- return results ;
63129 }
64130
65- return parseTestFilesRecursive ( dir ) ;
131+ return results ;
66132}
67133
68- function toTestDef ( testDef : any , defaultConfig : any , targetUri ?: string ) : TestCaseInvocation {
134+ function toTestCaseInvocation (
135+ testDef : any ,
136+ targetUri : any ,
137+ defaultConfig : any ,
138+ ) : TestCaseInvocation {
69139 const steps = testDef . steps ?? [ ] ;
70140 const route = testDef . testConfig ?. route ?? defaultConfig ?. route ?? "" ;
71141 const browsers : Browser [ ] = testDef . testConfig ?. browsers ??
72142 defaultConfig ?. browsers ?? [ Browser . CHROME ] ;
73143 return {
74144 testCase : {
145+ id : testDef . id ,
146+ prerequisiteTestCaseId : testDef . prerequisiteTestCaseId ,
75147 startUri : targetUri + route ,
76148 displayName : testDef . testName ,
77149 instructions : { steps } ,
0 commit comments