e2e.prod.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import { spawn } from 'child_process';
  2. import { accessSync, readFileSync, writeFileSync } from 'fs';
  3. import { dirname, join, relative } from 'path';
  4. import * as glob from 'glob';
  5. import { task } from 'gulp';
  6. import * as del from 'del';
  7. import { template } from 'lodash';
  8. import * as runSequence from 'run-sequence';
  9. import { argv } from 'yargs';
  10. import { DIST_E2E_COMPONENTS_ROOT, ES_2015, PROJECT_ROOT, SRC_ROOT, SRC_COMPONENTS_ROOT, SCRIPTS_ROOT } from '../constants';
  11. import { createTempTsConfig, createTimestamp, getFolderInfo, readFileAsync, runAppScriptsBuild, writeFileAsync, writePolyfills } from '../util';
  12. import * as pAll from 'p-all';
  13. task('e2e.prepare', (done: Function) => {
  14. runSequence('e2e.clean', 'e2e.polyfill', 'e2e.prepareSass', (err: any) => done(err));
  15. });
  16. task('e2e.prepareSass', (done: Function) => {
  17. const version = `E2E-${createTimestamp()}`;
  18. writeFileSync(join(SRC_ROOT, 'themes', 'version.scss'), `$ionic-version: "${version}";`);
  19. done();
  20. });
  21. task('e2e.prod', ['e2e.prepare'], (done: Function) => {
  22. // okay, first find out all of the e2e tests to run by finding all of the 'main.ts' files
  23. filterE2eTestfiles().then((filePaths: string[]) => {
  24. if (filePaths && filePaths.length > 0) {
  25. console.log(`Compiling ${filePaths.length} E2E tests ...`);
  26. return buildTests(filePaths);
  27. }
  28. }).then(() => {
  29. done();
  30. }).catch((err: Error) => {
  31. done(err);
  32. process.exit(1);
  33. });
  34. });
  35. function e2eComponentExists(folderInfo: any): boolean {
  36. let componentPath = join(SRC_COMPONENTS_ROOT, folderInfo.componentName, 'test', folderInfo.componentTest, 'app');
  37. try {
  38. accessSync(componentPath);
  39. } catch (e) {
  40. return false;
  41. }
  42. return true;
  43. }
  44. function filterE2eTestfiles() {
  45. return getE2eTestFiles().then((filePaths: string[]) => {
  46. const entryPoints = filePaths.map(filePath => {
  47. const directoryName = dirname(filePath);
  48. return join(directoryName, 'app', 'main.ts');
  49. });
  50. return entryPoints;
  51. }).then((entryPoints: string[]) => {
  52. const folderInfo = getFolderInfo();
  53. if (folderInfo && folderInfo.componentName && folderInfo.componentTest) {
  54. if (!e2eComponentExists(folderInfo)) {
  55. console.log('Cannot find E2E test ', join(folderInfo.componentName, 'test', folderInfo.componentTest), '. Make sure that the test exists and you are passing the correct folder.');
  56. return [];
  57. }
  58. const filtered = entryPoints.filter(entryPoint => {
  59. return entryPoint.indexOf(join(folderInfo.componentName, 'test', folderInfo.componentTest)) >= 0;
  60. });
  61. return filtered;
  62. }
  63. return entryPoints;
  64. });
  65. }
  66. function getE2eTestFiles() {
  67. return new Promise((resolve, reject) => {
  68. const mainGlob = join(SRC_COMPONENTS_ROOT, '*', 'test', '*', 'e2e.ts');
  69. glob(mainGlob, (err: Error, matches: string[]) => {
  70. if (err) {
  71. return reject(err);
  72. }
  73. resolve(matches);
  74. });
  75. });
  76. }
  77. function buildTests(filePaths: string[]) {
  78. const functions = filePaths.map(filePath => () => {
  79. return buildTest(filePath);
  80. });
  81. // Run 2 tests at a time unless the `concurrency` arg is passed
  82. let concurrentNumber = 2;
  83. if (argv.concurrency) {
  84. concurrentNumber = argv.concurrency;
  85. }
  86. return pAll(functions, {concurrency: concurrentNumber}).then(() => {
  87. // copy over all of the protractor tests to the correct location now
  88. return copyProtractorTestContent(filePaths);
  89. });
  90. }
  91. function buildTest(filePath: string) {
  92. const start = Date.now();
  93. const ionicAngularDir = join(process.cwd(), 'src');
  94. let appEntryPoint = filePath;
  95. let srcTestRoot = dirname(dirname(appEntryPoint));
  96. try {
  97. // check if the entry point exists, otherwise fall back to the legacy entry point without 'app' folder
  98. readFileSync(appEntryPoint);
  99. } catch (ex) {
  100. // the file doesn't exist, so use the legacy entry point
  101. appEntryPoint = join(dirname(dirname(appEntryPoint)), 'main.ts');
  102. srcTestRoot = dirname(appEntryPoint);
  103. }
  104. const relativePathFromComponents = relative(dirname(SRC_COMPONENTS_ROOT), srcTestRoot);
  105. const distTestRoot = join(process.cwd(), 'dist', 'e2e', relativePathFromComponents);
  106. const includeGlob = [join(ionicAngularDir, '**', '*.ts')];
  107. const pathToWriteFile = join(distTestRoot, 'tsconfig.json');
  108. const pathToReadFile = join(PROJECT_ROOT, 'tsconfig.json');
  109. createTempTsConfig(includeGlob, ES_2015, ES_2015, pathToReadFile, pathToWriteFile, { removeComments: true});
  110. const sassConfigPath = join('scripts', 'e2e', 'sass.config.js');
  111. const copyConfigPath = join('scripts', 'e2e', 'copy.config.js');
  112. const appNgModulePath = join(dirname(appEntryPoint), 'app.module.ts');
  113. const distDir = join(distTestRoot, 'www');
  114. const minifyCss = argv.minifyCss ? true : false;
  115. const minifyJs = argv.minifyJs ? true : false;
  116. const optimizeJs = argv.optimizeJs ? true : false;
  117. return runAppScriptsBuild(appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, argv.dev, minifyCss, minifyJs, optimizeJs).then(() => {
  118. const end = Date.now();
  119. console.log(`${filePath} took a total of ${(end - start) / 1000} seconds to build`);
  120. }).catch((err) => {
  121. console.log(`${err}`);
  122. });
  123. }
  124. function copyProtractorTestContent(filePaths: string[]): Promise<any> {
  125. const e2eTestPaths = filePaths.map(filePath => {
  126. return join(dirname(dirname(filePath)), 'e2e.ts');
  127. });
  128. return readE2ETestFiles(e2eTestPaths)
  129. .then((map: Map<string, string>) => {
  130. return applyTemplate(map);
  131. }).then((map: Map<string, string>) => {
  132. writeE2EJsFiles(map);
  133. });
  134. }
  135. function applyTemplate(filePathContent: Map<string, string>) {
  136. const buildConfig = require(join('..', '..', 'build', 'config'));
  137. const templateFileContent = readFileSync(join(SCRIPTS_ROOT, 'e2e', 'e2e.template.js'));
  138. const templater = template(templateFileContent.toString());
  139. const modifiedMap = new Map<string, string>();
  140. const platforms = ['android', 'ios', 'windows'];
  141. filePathContent.forEach((fileContent: string, filePath: string) => {
  142. const srcRelativePath = relative(SRC_ROOT, dirname(filePath));
  143. const wwwRelativePath = join(srcRelativePath, 'www');
  144. platforms.forEach(platform => {
  145. const platformContents = templater({
  146. contents: fileContent,
  147. buildConfig: buildConfig,
  148. relativePath: wwwRelativePath,
  149. platform: platform,
  150. relativePathBackwardsCompatibility: dirname(wwwRelativePath)
  151. });
  152. const newFilePath = join(wwwRelativePath, `${platform}.e2e.js`);
  153. modifiedMap.set(newFilePath, platformContents);
  154. });
  155. });
  156. return modifiedMap;
  157. }
  158. function writeE2EJsFiles(map: Map<string, string>) {
  159. const promises: Promise<any>[] = [];
  160. map.forEach((fileContent: string, filePath: string) => {
  161. const destination = join(process.cwd(), 'dist', 'e2e', filePath);
  162. promises.push(writeFileAsync(destination, fileContent));
  163. });
  164. return Promise.all(promises);
  165. }
  166. function readE2ETestFiles(mainFilePaths: string[]): Promise<Map<string, string>> {
  167. const e2eFiles = mainFilePaths.map(mainFilePath => {
  168. return join(dirname(mainFilePath), 'e2e.ts');
  169. });
  170. const promises: Promise<any>[] = [];
  171. const map = new Map<string, string>();
  172. for (const e2eFile of e2eFiles) {
  173. const promise = readE2EFile(e2eFile);
  174. promises.push(promise);
  175. promise.then((content: string) => {
  176. map.set(e2eFile, content);
  177. });
  178. }
  179. return Promise.all(promises).then(() => {
  180. return map;
  181. });
  182. }
  183. function readE2EFile(filePath: string) {
  184. return readFileAsync(filePath).then((content: string) => {
  185. // purge the import statement at the top
  186. const purgeImportRegex = /.*?import.*?'protractor';/g;
  187. return content.replace(purgeImportRegex, '');
  188. });
  189. }
  190. task('e2e.clean', (done: Function) => {
  191. // this is a super hack, but it works for now
  192. if (argv.skipClean) {
  193. return done();
  194. }
  195. del(['dist/e2e/**']).then(() => {
  196. done();
  197. }).catch(err => {
  198. done(err);
  199. });
  200. });
  201. task('e2e.polyfill', (done: Function) => {
  202. if (argv.skipPolyfill) {
  203. return done();
  204. }
  205. writePolyfills(join('dist', 'e2e', 'polyfills')).then(() => {
  206. done();
  207. }).catch(err => {
  208. done(err);
  209. });
  210. });
  211. task('e2e.openProd', (done: Function) => {
  212. runSequence('e2e.prod', 'e2e.open', (err: any) => done(err));
  213. });
  214. task('e2e.open', (done: Function) => {
  215. const folderInfo = getFolderInfo();
  216. if (folderInfo && folderInfo.componentName && folderInfo.componentTest) {
  217. const filePath = join(DIST_E2E_COMPONENTS_ROOT, folderInfo.componentName, 'test', folderInfo.componentTest, 'www', 'index.html');
  218. const spawnedCommand = spawn('open', [filePath]);
  219. spawnedCommand.on('close', (code: number) => {
  220. done();
  221. });
  222. } else {
  223. console.log(`Can't open without folder argument.`);
  224. }
  225. });