diff --git a/.travis.yml b/.travis.yml index 155ec7fe45..0e091ce1f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ env: matrix: # Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete. - CI_MODE=e2e + - CI_MODE=e2e_2 - CI_MODE=js - CI_MODE=saucelabs_required - CI_MODE=browserstack_required diff --git a/packages/platform-server/integrationtest/.gitignore b/packages/platform-server/integrationtest/.gitignore new file mode 100644 index 0000000000..372184be7d --- /dev/null +++ b/packages/platform-server/integrationtest/.gitignore @@ -0,0 +1,6 @@ +built/ +*/src/*.d.ts +*/src/*.js +**/*.ngfactory.ts +**/*.ngsummary.json +npm-debug.log \ No newline at end of file diff --git a/packages/platform-server/integrationtest/README.md b/packages/platform-server/integrationtest/README.md new file mode 100644 index 0000000000..318f84d823 --- /dev/null +++ b/packages/platform-server/integrationtest/README.md @@ -0,0 +1,7 @@ +To add a new server side rendering E2E test + +- Add a new server side rendered application to src/ +- Edit webpack.client.config.js to add new entry point for the new client bundle +- The index.html can access the client bundle from /built/.js +- Edit src/server.ts to add the server side application to a new URL +- Add a protractor test in e2e/ to test with the new URL diff --git a/packages/platform-server/integrationtest/build.sh b/packages/platform-server/integrationtest/build.sh new file mode 100755 index 0000000000..09c6c62d83 --- /dev/null +++ b/packages/platform-server/integrationtest/build.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +rm -rf built + +ngc + +# This is to mainlt copy the index.html to be packaged into the server. +cp -r src/* built/src + +# Bundle the server which hosts all the server side apps. +webpack --config webpack.server.config.js + +# Bundle the clients into individual bundles. +webpack --config webpack.client.config.js \ No newline at end of file diff --git a/packages/platform-server/integrationtest/e2e/helloworld-spec.ts b/packages/platform-server/integrationtest/e2e/helloworld-spec.ts new file mode 100644 index 0000000000..67c822ffbd --- /dev/null +++ b/packages/platform-server/integrationtest/e2e/helloworld-spec.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {browser, by, element} from 'protractor'; + +import {verifyNoBrowserErrors} from './util'; + +describe('Hello world E2E Tests', function() { + it('should display: Hello world!', function() { + // Load the page without waiting for Angular since it is not boostrapped automatically. + browser.driver.get(browser.baseUrl + 'helloworld'); + + const style = browser.driver.findElement(by.css('style[ng-transition="hlw"]')); + expect(style.getText()).not.toBeNull(); + + // Test the contents from the server. + const serverDiv = browser.driver.findElement(by.css('div')); + expect(serverDiv.getText()).toEqual('Hello world!'); + + // Bootstrap the client side app. + browser.executeScript('doBootstrap()'); + + // Retest the contents after the client bootstraps. + expect(element(by.css('div')).getText()).toEqual('Hello world!'); + + // Make sure the server styles got replaced by client side ones. + expect(element(by.css('style[ng-transition="hlw"]')).isPresent()).toBe(false); + expect(element(by.css('style')).getText()).toBe(''); + + // Make sure there were no client side errors. + verifyNoBrowserErrors(); + }); +}); diff --git a/packages/platform-server/integrationtest/e2e/protractor.config.js b/packages/platform-server/integrationtest/e2e/protractor.config.js new file mode 100644 index 0000000000..92cb61607c --- /dev/null +++ b/packages/platform-server/integrationtest/e2e/protractor.config.js @@ -0,0 +1,22 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +exports.config = { + specs: ['../built/e2e/*-spec.js'], + capabilities: { + browserName: 'chrome', + chromeOptions: { + args: ['--no-sandbox'], + binary: process.env.CHROME_BIN, + } + }, + directConnect: true, + baseUrl: 'http://localhost:9876/', + framework: 'jasmine', + useAllAngular2AppRoots: true +}; diff --git a/packages/platform-server/integrationtest/e2e/tsconfig.json b/packages/platform-server/integrationtest/e2e/tsconfig.json new file mode 100644 index 0000000000..c70d9a1cf5 --- /dev/null +++ b/packages/platform-server/integrationtest/e2e/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "outDir": "../built/e2e", + "types": ["jasmine"], + // TODO(alexeagle): was required for Protractor 4.0.11 + "skipLibCheck": true + } +} diff --git a/packages/platform-server/integrationtest/e2e/util.ts b/packages/platform-server/integrationtest/e2e/util.ts new file mode 100644 index 0000000000..3002af5d6b --- /dev/null +++ b/packages/platform-server/integrationtest/e2e/util.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/* tslint:disable:no-console */ +import * as webdriver from 'selenium-webdriver'; +declare var browser: any; +declare var expect: any; + +export function verifyNoBrowserErrors() { + browser.manage().logs().get('browser').then(function(browserLog: any[]) { + const errors: any[] = []; + browserLog.filter(logEntry => { + const msg = logEntry.message; + console.log('>> ' + msg); + if (logEntry.level.value >= webdriver.logging.Level.INFO.value) { + errors.push(msg); + } + }); + expect(errors).toEqual([]); + }); +} diff --git a/packages/platform-server/integrationtest/package.json b/packages/platform-server/integrationtest/package.json new file mode 100644 index 0000000000..cd3e46b656 --- /dev/null +++ b/packages/platform-server/integrationtest/package.json @@ -0,0 +1,41 @@ +{ + "name": "platform-server-integration", + "version": "0.0.0", + "license": "MIT", + "description": "Integration tests for @angular/platform-server", + "repository": { + "type": "git", + "url": "https://github.com/angular/angular.git" + }, + "dependencies": { + "@angular/common": "file:../../../dist/packages-dist/common", + "@angular/compiler": "file:../../../dist/packages-dist/compiler", + "@angular/compiler-cli": "file:../../../dist/packages-dist/compiler-cli", + "@angular/core": "file:../../../dist/packages-dist/core", + "@angular/http": "file:../../../dist/packages-dist/http", + "@angular/platform-browser": "file:../../../dist/packages-dist/platform-browser", + "@angular/platform-server": "file:../../../dist/packages-dist/platform-server", + "express": "^4.14.1", + "rxjs": "file:../../../node_modules/rxjs", + "typescript": "2.1.6", + "zone.js": "0.7.6" + }, + "devDependencies": { + "@types/jasmine": "2.5.41", + "babel-core": "^6.23.1", + "babel-loader": "^6.4.0", + "babel-preset-es2015": "^6.22.0", + "concurrently": "3.1.0", + "protractor": "file:../../../node_modules/protractor", + "raw-loader": "^0.5.1", + "webpack": "^2.2.1" + }, + "scripts": { + "postinstall": "webdriver-manager update", + "build": "./build.sh", + "test": "npm run build && concurrently \"npm run serve\" \"npm run protractor\" --kill-others --success first", + "serve": "node built/server-bundle.js", + "preprotractor": "tsc -p e2e", + "protractor": "protractor e2e/protractor.config.js" + } +} diff --git a/packages/platform-server/integrationtest/run_tests.sh b/packages/platform-server/integrationtest/run_tests.sh new file mode 100755 index 0000000000..033f6159d2 --- /dev/null +++ b/packages/platform-server/integrationtest/run_tests.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +cd `dirname $0` + +echo "#################################" +echo "Running platform-server end to end tests" +echo "#################################" + +npm install + +npm run test diff --git a/packages/platform-server/integrationtest/src/helloworld/app.server.ts b/packages/platform-server/integrationtest/src/helloworld/app.server.ts new file mode 100644 index 0000000000..c07d2f4837 --- /dev/null +++ b/packages/platform-server/integrationtest/src/helloworld/app.server.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {ServerModule} from '@angular/platform-server'; + +import {HelloWorldModule} from './app'; +import {HelloWorldComponent} from './hello-world.component'; + +@NgModule({ + bootstrap: [HelloWorldComponent], + imports: [HelloWorldModule, ServerModule], +}) +export class HelloWorldServerModule { +} diff --git a/packages/platform-server/integrationtest/src/helloworld/app.ts b/packages/platform-server/integrationtest/src/helloworld/app.ts new file mode 100644 index 0000000000..50a2898ae5 --- /dev/null +++ b/packages/platform-server/integrationtest/src/helloworld/app.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {BrowserModule} from '@angular/platform-browser'; + +import {HelloWorldComponent} from './hello-world.component'; + +@NgModule({ + declarations: [HelloWorldComponent], + bootstrap: [HelloWorldComponent], + imports: [BrowserModule.withServerTransition({appId: 'hlw'})], +}) +export class HelloWorldModule { +} diff --git a/packages/platform-server/integrationtest/src/helloworld/client.ts b/packages/platform-server/integrationtest/src/helloworld/client.ts new file mode 100644 index 0000000000..b2ecbecac4 --- /dev/null +++ b/packages/platform-server/integrationtest/src/helloworld/client.ts @@ -0,0 +1,17 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import 'zone.js/dist/zone.js'; + +import {enableProdMode} from '@angular/core'; +import {platformBrowser} from '@angular/platform-browser'; +import {HelloWorldModuleNgFactory} from './app.ngfactory'; + +window['doBootstrap'] = function() { + platformBrowser().bootstrapModuleFactory(HelloWorldModuleNgFactory); +}; diff --git a/packages/platform-server/integrationtest/src/helloworld/hello-world.component.ts b/packages/platform-server/integrationtest/src/helloworld/hello-world.component.ts new file mode 100644 index 0000000000..79e99e0063 --- /dev/null +++ b/packages/platform-server/integrationtest/src/helloworld/hello-world.component.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component} from '@angular/core'; + +@Component({ + selector: 'hello-world-app', + template: ` +
Hello {{ name }}!
+ `, + styles: [` + div { + font-weight: bold; + } + `] +}) +export class HelloWorldComponent { + name: string = 'world'; +} diff --git a/packages/platform-server/integrationtest/src/helloworld/index.html b/packages/platform-server/integrationtest/src/helloworld/index.html new file mode 100644 index 0000000000..9da04776ae --- /dev/null +++ b/packages/platform-server/integrationtest/src/helloworld/index.html @@ -0,0 +1,10 @@ + + + + Hello World + + + + + + diff --git a/packages/platform-server/integrationtest/src/server.ts b/packages/platform-server/integrationtest/src/server.ts new file mode 100644 index 0000000000..7f7341cde1 --- /dev/null +++ b/packages/platform-server/integrationtest/src/server.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/* tslint:disable:no-console */ +require('zone.js/dist/zone-node.js'); + +import {enableProdMode, NgModuleFactory} from '@angular/core'; +import {renderModuleFactory} from '@angular/platform-server'; +import * as express from 'express'; + +import {HelloWorldServerModuleNgFactory} from './helloworld/app.server.ngfactory'; +const helloworld = require('raw-loader!./helloworld/index.html'); + +const app = express(); + +function render(moduleFactory: NgModuleFactory, html: string) { + return (req, res) => { + renderModuleFactory(moduleFactory, { + document: html, + url: req.url, + }).then((response) => { res.send(response); }); + }; +} + +enableProdMode(); + +// Client bundles will be statically served from the built/ directory. +app.use('/built', express.static('built')); + +// Keep the browser logs free of errors. +app.get('/favicon.ico', (req, res) => { res.send(''); }); + +//-----------ADD YOUR SERVER SIDE RENDERED APP HERE ---------------------- +app.get('/helloworld', render(HelloWorldServerModuleNgFactory, helloworld)); + +app.listen(9876, function() { console.log('Server listening on port 9876!'); }); \ No newline at end of file diff --git a/packages/platform-server/integrationtest/tsconfig.json b/packages/platform-server/integrationtest/tsconfig.json new file mode 100644 index 0000000000..e728bdcd6e --- /dev/null +++ b/packages/platform-server/integrationtest/tsconfig.json @@ -0,0 +1,27 @@ +{ + "angularCompilerOptions": { + "annotationsAs": "static fields", + "annotateForClosureCompiler": true + }, + + "compilerOptions": { + "module": "es2015", + "moduleResolution": "node", + "strictNullChecks": true, + "target": "es6", + "noImplicitAny": false, + "sourceMap": false, + "experimentalDecorators": true, + "outDir": "built/src", + "declaration": true, + "typeRoots": ["node_modules/@types"] + }, + + "exclude": [ + "vendor", + "node_modules", + "built", + "dist", + "e2e" + ] +} diff --git a/packages/platform-server/integrationtest/webpack.client.config.js b/packages/platform-server/integrationtest/webpack.client.config.js new file mode 100644 index 0000000000..d8eda93db9 --- /dev/null +++ b/packages/platform-server/integrationtest/webpack.client.config.js @@ -0,0 +1,18 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +const path = require('path'); + +module.exports = { + entry: { + helloworld: './built/src/helloworld/client.js', + }, + output: {path: path.join(__dirname, 'built'), filename: '[name]-bundle.js'}, + module: {loaders: [{test: /\.js$/, loader: 'babel-loader?presets[]=es2015'}]}, + resolve: {extensions: ['.js']} +}; diff --git a/packages/platform-server/integrationtest/webpack.server.config.js b/packages/platform-server/integrationtest/webpack.server.config.js new file mode 100644 index 0000000000..286d8df003 --- /dev/null +++ b/packages/platform-server/integrationtest/webpack.server.config.js @@ -0,0 +1,14 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +module.exports = { + target: 'node', + entry: './built/src/server.js', + output: {filename: './built/server-bundle.js'}, + resolve: {extensions: ['.js']}, +}; diff --git a/packages/tsconfig.json b/packages/tsconfig.json index e3fd4343de..4675584dd1 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -26,6 +26,7 @@ "types": ["angularjs"] }, "exclude": [ - "compiler-cli/integrationtest" + "compiler-cli/integrationtest", + "platform-server/integrationtest" ] } diff --git a/scripts/ci/build.sh b/scripts/ci/build.sh index 0cd774fd89..bf6279296b 100755 --- a/scripts/ci/build.sh +++ b/scripts/ci/build.sh @@ -38,7 +38,7 @@ travisFoldEnd "tsc a bunch of useless stuff" # Build integration tests -if [[ ${CI_MODE:-} == "e2e" ]]; then +if [[ ${CI_MODE:-} == "e2e_2" ]]; then travisFoldStart "build.integration" cd "`dirname $0`/../../integration" ./build_rxjs_es6.sh diff --git a/scripts/ci/install.sh b/scripts/ci/install.sh index 93420bbcaa..a817064ecc 100755 --- a/scripts/ci/install.sh +++ b/scripts/ci/install.sh @@ -41,7 +41,7 @@ travisFoldStart "npm-install" travisFoldEnd "npm-install" -if [[ ${TRAVIS} && (${CI_MODE} == "e2e" || ${CI_MODE} == "aio" || ${CI_MODE} == "docs_test") ]]; then +if [[ ${TRAVIS} && (${CI_MODE} == "e2e" || ${CI_MODE} == "e2e_2" || ${CI_MODE} == "aio" || ${CI_MODE} == "docs_test") ]]; then # Install version of yarn that we are locked against travisFoldStart "install-yarn" curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "${YARN_VERSION}" @@ -61,7 +61,7 @@ fi # Install Chromium -if [[ ${CI_MODE} == "js" || ${CI_MODE} == "e2e" || ${CI_MODE} == "aio" ]]; then +if [[ ${CI_MODE} == "js" || ${CI_MODE} == "e2e" || ${CI_MODE} == "e2e_2" || ${CI_MODE} == "aio" ]]; then travisFoldStart "install-chromium" ( ${thisDir}/install-chromium.sh diff --git a/scripts/ci/test-e2e-2.sh b/scripts/ci/test-e2e-2.sh new file mode 100755 index 0000000000..13735842a1 --- /dev/null +++ b/scripts/ci/test-e2e-2.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# Second shard for the e2e tests. Balance it with runtime of test-e2e.sh + +set -u -e -o pipefail + +# Setup environment +readonly thisDir=$(cd $(dirname $0); pwd) +source ${thisDir}/_travis-fold.sh + + +travisFoldStart "test.e2e.buildPackages" + ./build.sh +travisFoldEnd "test.e2e.buildPackages" + + +if [[ ${TRAVIS:-} ]]; then + travisFoldStart "test.e2e.xvfb-start" + sh -e /etc/init.d/xvfb start + travisFoldEnd "test.e2e.xvfb-start" +fi + + +travisFoldStart "test.e2e.integration" + ./integration/run_tests.sh +travisFoldEnd "test.e2e.integration" + + +travisFoldStart "test.e2e.offlineCompiler" + #TODO(alexeagle): move offline_compiler_test to integration/ + ${thisDir}/offline_compiler_test.sh +travisFoldEnd "test.e2e.offlineCompiler" + +travisFoldStart "test.e2e.platform-server" + ./packages/platform-server/integrationtest/run_tests.sh +travisFoldEnd "test.e2e.platform-server" diff --git a/scripts/ci/test-e2e.sh b/scripts/ci/test-e2e.sh index 5b8cbc58dc..00cb0256b1 100755 --- a/scripts/ci/test-e2e.sh +++ b/scripts/ci/test-e2e.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# First shard for the e2e tests. Balance it with runtime of test-e2e-2.sh + set -u -e -o pipefail # Setup environment @@ -18,18 +20,6 @@ if [[ ${TRAVIS:-} ]]; then travisFoldEnd "test.e2e.xvfb-start" fi - -travisFoldStart "test.e2e.integration" - ./integration/run_tests.sh -travisFoldEnd "test.e2e.integration" - - -travisFoldStart "test.e2e.offlineCompiler" - #TODO(alexeagle): move offline_compiler_test to integration/ - ${thisDir}/offline_compiler_test.sh -travisFoldEnd "test.e2e.offlineCompiler" - - travisFoldStart "test.e2e.publicApi" $(npm bin)/gulp public-api:enforce travisFoldEnd "test.e2e.publicApi" diff --git a/scripts/ci/test.sh b/scripts/ci/test.sh index 67e4f63c4d..20aee520d4 100755 --- a/scripts/ci/test.sh +++ b/scripts/ci/test.sh @@ -22,6 +22,9 @@ case ${CI_MODE} in e2e) ${thisDir}/test-e2e.sh ;; + e2e_2) + ${thisDir}/test-e2e-2.sh + ;; saucelabs_required) ${thisDir}/test-saucelabs.sh ;;