From db873341a3b0345789388f3597de50d658daf457 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 1 Oct 2018 23:13:48 +0200 Subject: [PATCH] build: fix e2e rate limit failures (#13376) * Since the `webdriver-manager` always tries to query the versions of all Selenium drivers, we currently run into rate limit failures that cause the E2E tests to not run properly. --- src/e2e-app/tsconfig-build.json | 29 ++++++++------- tools/gulp/tasks/e2e.ts | 62 ++++++++++++++++++--------------- tools/gulp/util/task_helpers.ts | 29 ++++++++------- 3 files changed, 65 insertions(+), 55 deletions(-) diff --git a/src/e2e-app/tsconfig-build.json b/src/e2e-app/tsconfig-build.json index eee34da736d..c41f2f49b87 100644 --- a/src/e2e-app/tsconfig-build.json +++ b/src/e2e-app/tsconfig-build.json @@ -15,8 +15,11 @@ "moduleResolution": "node", "noEmitOnError": true, "noImplicitAny": true, - "rootDir": ".", - "outDir": ".", + "rootDirs": [ + "../..", + "./" + ], + "outDir": "../../dist/", "sourceMap": true, "target": "es5", "typeRoots": [ @@ -24,15 +27,15 @@ ], "baseUrl": ".", "paths": { - "@angular/cdk/*": ["./cdk/*"], - "@angular/material": ["./material"], - "@angular/material/*": ["./material/*"], - "@angular/material-experimental/*": ["./material-experimental/*"], - "@angular/material-experimental": ["./material-experimental/"], - "@angular/cdk-experimental/*": ["./cdk-experimental/*"], - "@angular/cdk-experimental": ["./cdk-experimental/"], - "@angular/material-moment-adapter": ["./material-moment-adapter"], - "@angular/material-examples": ["./material-examples"] + "@angular/cdk/*": ["../../dist/releases/cdk/*"], + "@angular/material": ["../../dist/releases/material"], + "@angular/material/*": ["../../dist/releases/material/*"], + "@angular/material-experimental/*": ["../../dist/releases/material-experimental/*"], + "@angular/material-experimental": ["../../dist/releases/material-experimental/"], + "@angular/cdk-experimental/*": ["../../dist/releases/cdk-experimental/*"], + "@angular/cdk-experimental": ["../../dist/releases/cdk-experimental/"], + "@angular/material-moment-adapter": ["../../dist/releases/material-moment-adapter"], + "@angular/material-examples": ["../../dist/releases/material-examples"] } }, "files": [ @@ -42,7 +45,7 @@ "./system-config.ts" ], "angularCompilerOptions": { - "skipMetadataEmit": true, - "fullTemplateTypeCheck": true + "fullTemplateTypeCheck": true, + "enableResourceInlining": true } } diff --git a/tools/gulp/tasks/e2e.ts b/tools/gulp/tasks/e2e.ts index e68d1a4af4e..78b387635d3 100644 --- a/tools/gulp/tasks/e2e.ts +++ b/tools/gulp/tasks/e2e.ts @@ -1,26 +1,24 @@ import {task} from 'gulp'; -import {join} from 'path'; -import {ngcBuildTask, copyTask, execNodeTask, serverTask} from '../util/task_helpers'; -import {copySync} from 'fs-extra'; import {buildConfig, sequenceTask, triggerLivereload, watchFiles} from 'material2-build-tools'; +import {join} from 'path'; +import {copyTask, execNodeTask, ngcBuildTask, serverTask} from '../util/task_helpers'; // There are no type definitions available for these imports. const gulpConnect = require('gulp-connect'); const {outputDir, packagesDir, projectDir} = buildConfig; -/** Path to the directory where all releases are created. */ -const releasesDir = join(outputDir, 'releases'); - const appDir = join(packagesDir, 'e2e-app'); const e2eTestDir = join(projectDir, 'e2e'); -const outDir = join(outputDir, 'packages', 'e2e-app'); + +/** + * The output of the e2e app will preserve the directory structure because otherwise NGC is not + * able to generate factory files for the release output and node modules. + */ +const outDir = join(outputDir, 'src/e2e-app'); const PROTRACTOR_CONFIG_PATH = join(projectDir, 'test/protractor.conf.js'); -const tsconfigPath = join(outDir, 'tsconfig-build.json'); - -/** Glob that matches all files that need to be copied to the output folder. */ -const assetsGlob = join(appDir, '**/*.+(html|css|json|ts)'); +const tsconfigPath = join(appDir, 'tsconfig-build.json'); /** Builds and serves the e2e-app and runs protractor once the e2e-app is ready. */ task('e2e', sequenceTask( @@ -68,12 +66,11 @@ task('e2e-app:build', sequenceTask( 'material-moment-adapter:build-release', 'material-examples:build-release' ], - ['e2e-app:copy-release', 'e2e-app:copy-assets'], - 'e2e-app:build-ts' + ['e2e-app:copy-index-html', 'e2e-app:build-ts'] )); -/** Task that copies all required assets to the output folder. */ -task('e2e-app:copy-assets', copyTask(assetsGlob, outDir)); +/** Task that copies the e2e-app index HTML file to the output. */ +task('e2e-app:copy-index-html', copyTask(join(appDir, 'index.html'), outDir)); /** Task that builds the TypeScript sources. Those are compiled inside of the dist folder. */ task('e2e-app:build-ts', ngcBuildTask(tsconfigPath)); @@ -84,13 +81,31 @@ task(':watch:e2eapp', () => { }); /** Ensures that protractor and webdriver are set up to run. */ -task(':test:protractor:setup', execNodeTask('protractor', 'webdriver-manager', ['update'])); +task(':test:protractor:setup', execNodeTask( + // Disable download of the gecko selenium driver because otherwise the webdriver + // manager queries GitHub for the latest version and will result in rate limit failures. + 'protractor', 'webdriver-manager', ['update', '--gecko', 'false'])); /** Runs protractor tests (assumes that server is already running. */ task(':test:protractor', execNodeTask('protractor', [PROTRACTOR_CONFIG_PATH])); -/** Starts up the e2e app server. */ -task(':serve:e2eapp', serverTask(outDir, false)); +/** Starts up the e2e app server and rewrites the HTTP requests to properly work with AOT. */ +task(':serve:e2eapp', serverTask(outDir, false, [ + // Rewrite each request for .ngfactory files which are outside of the e2e-app to the **actual** + // path. This is necessary because NGC cannot generate factory files for the node modules + // and release output. If we work around it by adding multiple root directories, the directory + // structure would be messed up, so we need to go this way for now (until Ivy). + { from: '^/((?:dist|node_modules)/.*\.ngfactory\.js)$', to: '/dist/$1' }, + // Rewrite the node_modules/ and dist/ folder to the real paths. Otherwise we would need + // to copy the required modules to the serve output. If dist/ is explicitly requested, we + // should redirect to the actual dist path because by default we fall back to the e2e output. + { from: '^/node_modules/(.*)$', to: '/node_modules/$1' }, + { from: '^/dist/(.*)$', to: '/dist/$1' }, + // Rewrite every path that doesn't point to a specific file to the e2e output. + // This is necessary for Angular's routing using the HTML5 History API. + { from: '^/[^.]+$', to: `/dist/src/e2e-app/index.html`}, + { from: '^(.*)$', to: `/dist/src/e2e-app/$1` }, +])); /** Terminates the e2e app server */ task(':serve:e2eapp:stop', gulpConnect.serverClose); @@ -103,14 +118,3 @@ task('serve:e2eapp', sequenceTask('e2e-app:build', ':serve:e2eapp')); * This should only be used when running e2e tests locally. */ task('serve:e2eapp:watch', ['serve:e2eapp', 'material:watch', ':watch:e2eapp']); - -// As a workaround for https://github.com/angular/angular/issues/12249, we need to -// copy the Material and CDK ESM output inside of the demo-app output. -task('e2e-app:copy-release', () => { - copySync(join(releasesDir, 'cdk'), join(outDir, 'cdk')); - copySync(join(releasesDir, 'material'), join(outDir, 'material')); - copySync(join(releasesDir, 'cdk-experimental'), join(outDir, 'cdk-experimental')); - copySync(join(releasesDir, 'material-experimental'), join(outDir, 'material-experimental')); - copySync(join(releasesDir, 'material-examples'), join(outDir, 'material-examples')); - copySync(join(releasesDir, 'material-moment-adapter'), join(outDir, 'material-moment-adapter')); -}); diff --git a/tools/gulp/util/task_helpers.ts b/tools/gulp/util/task_helpers.ts index 241f90fbabd..9ef8556e2ff 100644 --- a/tools/gulp/util/task_helpers.ts +++ b/tools/gulp/util/task_helpers.ts @@ -126,9 +126,22 @@ export function cleanTask(glob: string) { * Create a task that serves a given directory in the project. * The server rewrites all node_module/ or dist/ requests to the correct directory. */ -export function serverTask(packagePath: string, livereload = true) { - // The http-rewrite-middlware only supports relative paths as rewrite destinations. +export function serverTask(packagePath: string, livereload = true, + rewrites?: {from: string, to: string}[]) { + + // The http-rewrite-middleware only supports relative paths as rewrite destinations. const relativePath = path.relative(projectDir, packagePath); + const defaultHttpRewrites = [ + // Rewrite the node_modules/ and dist/ folder to the real paths. This is a trick to + // avoid that those folders will be rewritten to the specified package path. + { from: '^/node_modules/(.*)$', to: '/node_modules/$1' }, + { from: '^/dist/(.*)$', to: '/dist/$1' }, + // Rewrite every path that doesn't point to a specific file to the index.html file. + // This is necessary for Angular's routing using the HTML5 History API. + { from: '^/[^.]+$', to: `/${relativePath}/index.html`}, + // Rewrite any path that didn't match a pattern before to the specified package path. + { from: '^(.*)$', to: `/${relativePath}/$1` }, + ]; return () => { gulpConnect.server({ @@ -137,17 +150,7 @@ export function serverTask(packagePath: string, livereload = true) { port: 4200, host: '0.0.0.0', middleware: () => { - return [httpRewrite.getMiddleware([ - // Rewrite the node_modules/ and dist/ folder to the real paths. This is a trick to - // avoid that those folders will be rewritten to the specified package path. - { from: '^/node_modules/(.*)$', to: '/node_modules/$1' }, - { from: '^/dist/(.*)$', to: '/dist/$1' }, - // Rewrite every path that doesn't point to a specific file to the index.html file. - // This is necessary for Angular's routing using the HTML5 History API. - { from: '^/[^.]+$', to: `/${relativePath}/index.html`}, - // Rewrite any path that didn't match a pattern before to the specified package path. - { from: '^(.*)$', to: `/${relativePath}/$1` }, - ])]; + return [httpRewrite.getMiddleware(rewrites || defaultHttpRewrites)]; } }); };