Compare commits

...

9 Commits
4.4.6 ... 4.4.x

Author SHA1 Message Date
018750154d test: fix firebase deployment script test
When I fixed the project id in 2c4850dc58,
I didn't realize we had a test that verified the wrong behavior.
2018-05-04 15:10:17 -07:00
b19216d58b fix(aio): correct project id for deployment of archive sites 2018-05-03 15:09:17 -07:00
84fc1a3663 docs: add changelog for the 4.4.7 release 2018-04-16 02:00:31 -06:00
1c40be26c6 release: cut the 4.4.7 release 2018-04-16 02:00:02 -06:00
2c5cf19c6d fix(core): use appropriate inert document strategy for Firefox & Safari (#22077)
Both Firefox and Safari are vulnerable to XSS if we use an inert document
created via `document.implementation.createHTMLDocument()`.

Now we check for those vulnerabilities and then use a DOMParser or XHR
strategy if needed.

Further the platform-server has its own library for parsing HTML, so we
sniff for that (by checking whether DOMParser exists) and fall back to
the standard strategy.

Thanks to @cure53 for the heads up on this issue.
2018-02-13 10:05:14 -08:00
0dacf6d5f1 ci: use sudo: false on Travis (#21641)
Related to #21422.

PR Close #21641
2018-02-11 21:18:03 +02:00
e9f1d44015 ci: downgrade Chromium to a version that does not cause flakes
There seems to be some issue that causes Chrome/ChromeDriver to
unexpectedly reload during the aio e2e tests, causing flakes. It is not
clear what exactly is causing the reloading, but to the best of my
knowledge it is something inside Chrome or ChromeDriver.

Pinning Chrome to r494239 (between 62.0.3185.0 and 62.0.3186.0) fixes
the flakes.

Fixes #20159
2018-02-11 21:18:03 +02:00
1d9024ee9a build: pin ChromeDriver version (#20940)
Since our version of Chromium is also pinned, a new ChromeDriver (that
drops support for our Chromium version) can cause random (and unrelated
to the corresponding changes) errors on CI.
This commit pins the version of ChromeDriver and it should now be
manually upgraded to a vrsion that is compatible with th currently used
Chromium version.

PR Close #20940
2018-02-11 21:18:03 +02:00
6a6164ab4f revert: ci: use chrome stable (#18307)
This reverts commit 8bcb268140.
2018-02-11 19:39:55 +02:00
20 changed files with 400 additions and 109 deletions

View File

@ -1,12 +1,10 @@
language: node_js
sudo: false
# force trusty as Google Chrome addon is not supported on Precise
dist: trusty
node_js:
- '6.9.5'
addons:
chrome: stable
# firefox: "38.0"
apt:
sources:

View File

@ -1,3 +1,13 @@
<a name="4.4.7"></a>
## [4.4.7](https://github.com/angular/angular/compare/4.4.6...4.4.7) (2018-04-16)
### Bug Fixes
* **core:** use appropriate inert document strategy for Firefox & Safari ([#22077](https://github.com/angular/angular/issues/22077)) ([2c5cf19](https://github.com/angular/angular/commit/2c5cf19))
<a name="4.4.6"></a>
## [4.4.6](https://github.com/angular/angular/compare/4.4.5...4.4.6) (2017-10-18)

View File

@ -30,8 +30,14 @@ module.exports = function (config) {
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
browsers: ['CustomChrome'],
browserNoActivityTimeout: 60000,
singleRun: false
singleRun: false,
customLaunchers: {
CustomChrome: {
base: 'Chrome',
flags: process.env.TRAVIS && ['--no-sandbox']
}
}
});
};

View File

@ -57,7 +57,7 @@
"~~check-env": "node scripts/check-environment",
"~~build": "ng build --target=production --environment=stable -sm --build-optimizer",
"post~~build": "yarn sw-manifest && yarn sw-copy",
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false"
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG"
},
"engines": {
"node": ">=6.9.5 <7.0.0",

View File

@ -12,7 +12,8 @@ exports.config = {
browserName: 'chrome',
// For Travis
chromeOptions: {
binary: process.env.CHROME_BIN
binary: process.env.CHROME_BIN,
args: ['--no-sandbox']
}
},
directConnect: true,

View File

@ -67,7 +67,7 @@ case $deployEnv in
readonly firebaseToken=$FIREBASE_TOKEN
;;
archive)
readonly projectId=angular-io-${majorVersion}
readonly projectId=v${majorVersion}-angular-io
readonly deployedUrl=https://v${majorVersion}.angular.io/
readonly firebaseToken=$FIREBASE_TOKEN
;;

View File

@ -94,7 +94,7 @@ Deployment URL : https://angular.io/"
)
expected="Git branch : 2.4.x
Build/deploy mode : archive
Firebase project : angular-io-2
Firebase project : v2-angular-io
Deployment URL : https://v2.angular.io/"
check "$actual" "$expected"
)

View File

@ -17,8 +17,16 @@ const printer = require('lighthouse/lighthouse-cli/printer');
const config = require('lighthouse/lighthouse-core/config/default.js');
// Constants
const CHROME_LAUNCH_OPTS = {};
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
// Specify the path and flags for Chrome on Travis
if (process.env.TRAVIS) {
process.env.LIGHTHOUSE_CHROMIUM_PATH = process.env.CHROME_BIN;
CHROME_LAUNCH_OPTS.chromeFlags = ['--no-sandbox'];
}
// Run
_main(process.argv.slice(2));
@ -66,7 +74,7 @@ function ignoreHttpsAudits(config) {
}
function launchChromeAndRunLighthouse(url, flags, config) {
return chromeLauncher.launch().then(chrome => {
return chromeLauncher.launch(CHROME_LAUNCH_OPTS).then(chrome => {
flags.port = chrome.port;
return lighthouse(url, flags, config).
then(results => chrome.kill().then(() => results)).

View File

@ -6,7 +6,7 @@
"scripts": {
"http-server": "http-server",
"protractor": "protractor",
"webdriver:update": "webdriver-manager update --standalone false --gecko false",
"webdriver:update": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG",
"postinstall": "yarn webdriver:update"
},
"keywords": [],

View File

@ -20,7 +20,12 @@ exports.config = {
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
'browserName': 'chrome',
// For Travis
chromeOptions: {
binary: process.env.CHROME_BIN,
args: ['--no-sandbox']
}
},
// Framework to use. Jasmine is recommended.

View File

@ -23,7 +23,7 @@
"protractor": "file:../../node_modules/protractor"
},
"scripts": {
"postinstall": "webdriver-manager update --gecko false",
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
"closure": "java -jar node_modules/google-closure-compiler/compiler.jar --flagfile closure.conf",
"test": "ngc && yarn run closure && concurrently \"yarn run serve\" \"yarn run protractor\" --kill-others --success first",
"serve": "lite-server -c e2e/browser.config.json",

View File

@ -4,7 +4,7 @@
"version": "0.0.0",
"license": "MIT",
"scripts": {
"postinstall": "webdriver-manager update --gecko false",
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
"test": "concurrently \"npm run serve\" \"npm run protractor\" --kill-others --success first",
"serve": "lite-server -c bs-config.e2e.json",
"preprotractor": "tsc -p e2e",

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "4.4.6",
"version": "4.4.7",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
@ -18,7 +18,8 @@
},
"scripts": {
"preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('Please use Yarn instead of NPM to install dependencies. See: https://yarnpkg.com/lang/en/docs/install/')\"",
"postinstall": "webdriver-manager update --gecko false",
"postinstall": "yarn update-webdriver",
"update-webdriver": "webdriver-manager update --gecko false $CHROMEDRIVER_VERSION_ARG",
"check-env": "gulp check-env"
},
"dependencies": {

View File

@ -10,35 +10,9 @@ import {isDevMode} from '@angular/core';
import {DomAdapter, getDOM} from '../dom/dom_adapter';
import {InertBodyHelper} from './inert_body';
import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer';
/** A <body> element that can be safely used to parse untrusted HTML. Lazily initialized below. */
let inertElement: HTMLElement|null = null;
/** Lazily initialized to make sure the DOM adapter gets set before use. */
let DOM: DomAdapter = null !;
/** Returns an HTML element that is guaranteed to not execute code when creating elements in it. */
function getInertElement() {
if (inertElement) return inertElement;
DOM = getDOM();
// Prefer using <template> element if supported.
const templateEl = DOM.createElement('template');
if ('content' in templateEl) return templateEl;
const doc = DOM.createHtmlDocument();
inertElement = DOM.querySelector(doc, 'body');
if (inertElement == null) {
// usually there should be only one body element in the document, but IE doesn't have any, so we
// need to create one.
const html = DOM.createElement('html', doc);
inertElement = DOM.createElement('body', doc);
DOM.appendChild(html, inertElement);
DOM.appendChild(doc, html);
}
return inertElement;
}
function tagSet(tags: string): {[k: string]: boolean} {
const res: {[k: string]: boolean} = {};
for (const t of tags.split(',')) res[t] = true;
@ -121,53 +95,54 @@ class SanitizingHtmlSerializer {
// because characters were re-encoded.
public sanitizedSomething = false;
private buf: string[] = [];
private DOM = getDOM();
sanitizeChildren(el: Element): string {
// This cannot use a TreeWalker, as it has to run on Angular's various DOM adapters.
// However this code never accesses properties off of `document` before deleting its contents
// again, so it shouldn't be vulnerable to DOM clobbering.
let current: Node = el.firstChild !;
let current: Node = this.DOM.firstChild(el) !;
while (current) {
if (DOM.isElementNode(current)) {
if (this.DOM.isElementNode(current)) {
this.startElement(current as Element);
} else if (DOM.isTextNode(current)) {
this.chars(DOM.nodeValue(current) !);
} else if (this.DOM.isTextNode(current)) {
this.chars(this.DOM.nodeValue(current) !);
} else {
// Strip non-element, non-text nodes.
this.sanitizedSomething = true;
}
if (DOM.firstChild(current)) {
current = DOM.firstChild(current) !;
if (this.DOM.firstChild(current)) {
current = this.DOM.firstChild(current) !;
continue;
}
while (current) {
// Leaving the element. Walk up and to the right, closing tags as we go.
if (DOM.isElementNode(current)) {
if (this.DOM.isElementNode(current)) {
this.endElement(current as Element);
}
let next = checkClobberedElement(current, DOM.nextSibling(current) !);
let next = this.checkClobberedElement(current, this.DOM.nextSibling(current) !);
if (next) {
current = next;
break;
}
current = checkClobberedElement(current, DOM.parentElement(current) !);
current = this.checkClobberedElement(current, this.DOM.parentElement(current) !);
}
}
return this.buf.join('');
}
private startElement(element: Element) {
const tagName = DOM.nodeName(element).toLowerCase();
const tagName = this.DOM.nodeName(element).toLowerCase();
if (!VALID_ELEMENTS.hasOwnProperty(tagName)) {
this.sanitizedSomething = true;
return;
}
this.buf.push('<');
this.buf.push(tagName);
DOM.attributeMap(element).forEach((value: string, attrName: string) => {
this.DOM.attributeMap(element).forEach((value: string, attrName: string) => {
const lower = attrName.toLowerCase();
if (!VALID_ATTRS.hasOwnProperty(lower)) {
this.sanitizedSomething = true;
@ -186,7 +161,7 @@ class SanitizingHtmlSerializer {
}
private endElement(current: Element) {
const tagName = DOM.nodeName(current).toLowerCase();
const tagName = this.DOM.nodeName(current).toLowerCase();
if (VALID_ELEMENTS.hasOwnProperty(tagName) && !VOID_ELEMENTS.hasOwnProperty(tagName)) {
this.buf.push('</');
this.buf.push(tagName);
@ -195,14 +170,14 @@ class SanitizingHtmlSerializer {
}
private chars(chars: string) { this.buf.push(encodeEntities(chars)); }
}
function checkClobberedElement(node: Node, nextNode: Node): Node {
if (nextNode && DOM.contains(node, nextNode)) {
throw new Error(
`Failed to sanitize html because the element is clobbered: ${DOM.getOuterHTML(node)}`);
checkClobberedElement(node: Node, nextNode: Node): Node {
if (nextNode && this.DOM.contains(node, nextNode)) {
throw new Error(
`Failed to sanitize html because the element is clobbered: ${this.DOM.getOuterHTML(node)}`);
}
return nextNode;
}
return nextNode;
}
// Regular Expressions for parsing tags and attributes
@ -233,33 +208,20 @@ function encodeEntities(value: string) {
.replace(/>/g, '&gt;');
}
/**
* When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1'
* attribute to declare ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo').
*
* This is undesirable since we don't want to allow any of these custom attributes. This method
* strips them all.
*/
function stripCustomNsAttrs(el: Element) {
DOM.attributeMap(el).forEach((_, attrName) => {
if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) {
DOM.removeAttribute(el, attrName);
}
});
for (const n of DOM.childNodesAsList(el)) {
if (DOM.isElementNode(n)) stripCustomNsAttrs(n as Element);
}
}
let inertBodyHelper: InertBodyHelper;
/**
* Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to
* the DOM in a browser environment.
*/
export function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string {
const DOM = getDOM();
let inertBodyElement: HTMLElement|null = null;
try {
const containerEl = getInertElement();
inertBodyHelper = inertBodyHelper || new InertBodyHelper(defaultDoc, DOM);
// Make sure unsafeHtml is actually a string (TypeScript types are not enforced at runtime).
let unsafeHtml = unsafeHtmlInput ? String(unsafeHtmlInput) : '';
inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml);
// mXSS protection. Repeatedly parse the document to make sure it stabilizes, so that a browser
// trying to auto-correct incorrect HTML cannot cause formerly inert HTML to become dangerous.
@ -273,31 +235,25 @@ export function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string {
mXSSAttempts--;
unsafeHtml = parsedHtml;
DOM.setInnerHTML(containerEl, unsafeHtml);
if (defaultDoc.documentMode) {
// strip custom-namespaced attributes on IE<=11
stripCustomNsAttrs(containerEl);
}
parsedHtml = DOM.getInnerHTML(containerEl);
parsedHtml = DOM.getInnerHTML(inertBodyElement);
inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml);
} while (unsafeHtml !== parsedHtml);
const sanitizer = new SanitizingHtmlSerializer();
const safeHtml = sanitizer.sanitizeChildren(DOM.getTemplateContent(containerEl) || containerEl);
// Clear out the body element.
const parent = DOM.getTemplateContent(containerEl) || containerEl;
for (const child of DOM.childNodesAsList(parent)) {
DOM.removeChild(parent, child);
}
const safeHtml =
sanitizer.sanitizeChildren(DOM.getTemplateContent(inertBodyElement) || inertBodyElement);
if (isDevMode() && sanitizer.sanitizedSomething) {
DOM.log('WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).');
}
return safeHtml;
} catch (e) {
} finally {
// In case anything goes wrong, clear out inertElement to reset the entire DOM structure.
inertElement = null;
throw e;
if (inertBodyElement) {
const parent = DOM.getTemplateContent(inertBodyElement) || inertBodyElement;
for (const child of DOM.childNodesAsList(parent)) {
DOM.removeChild(parent, child);
}
}
}
}

View File

@ -0,0 +1,171 @@
/**
* @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 {DomAdapter, getDOM} from '../dom/dom_adapter';
/**
* This helper class is used to get hold of an inert tree of DOM elements containing dirty HTML
* that needs sanitizing.
* Depending upon browser support we must use one of three strategies for doing this.
* Support: Safari 10.x -> XHR strategy
* Support: Firefox -> DomParser strategy
* Default: InertDocument strategy
*/
export class InertBodyHelper {
private inertBodyElement: HTMLElement;
constructor(private defaultDoc: any, private DOM: DomAdapter) {
const inertDocument = this.DOM.createHtmlDocument();
this.inertBodyElement = inertDocument.body;
if (this.inertBodyElement == null) {
// usually there should be only one body element in the document, but IE doesn't have any, so
// we need to create one.
const inertHtml = this.DOM.createElement('html', inertDocument);
this.inertBodyElement = this.DOM.createElement('body', inertDocument);
this.DOM.appendChild(inertHtml, this.inertBodyElement);
this.DOM.appendChild(inertDocument, inertHtml);
}
this.DOM.setInnerHTML(
this.inertBodyElement, '<svg><g onload="this.parentNode.remove()"></g></svg>');
if (this.inertBodyElement.querySelector && !this.inertBodyElement.querySelector('svg')) {
// We just hit the Safari 10.1 bug - which allows JS to run inside the SVG G element
// so use the XHR strategy.
this.getInertBodyElement = this.getInertBodyElement_XHR;
return;
}
this.DOM.setInnerHTML(
this.inertBodyElement, '<svg><p><style><img src="</style><img src=x onerror=alert(1)//">');
if (this.inertBodyElement.querySelector && this.inertBodyElement.querySelector('svg img')) {
// We just hit the Firefox bug - which prevents the inner img JS from being sanitized
// so use the DOMParser strategy, if it is available.
// If the DOMParser is not available then we are not in Firefox (Server/WebWorker?) so we
// fall through to the default strategy below.
if (isDOMParserAvailable()) {
this.getInertBodyElement = this.getInertBodyElement_DOMParser;
return;
}
}
// None of the bugs were hit so it is safe for us to use the default InertDocument strategy
this.getInertBodyElement = this.getInertBodyElement_InertDocument;
}
/**
* Get an inert DOM element containing DOM created from the dirty HTML string provided.
* The implementation of this is determined in the constructor, when the class is instantiated.
*/
getInertBodyElement: (html: string) => HTMLElement | null;
/**
* Use XHR to create and fill an inert body element (on Safari 10.1)
* See
* https://github.com/cure53/DOMPurify/blob/a992d3a75031cb8bb032e5ea8399ba972bdf9a65/src/purify.js#L439-L449
*/
private getInertBodyElement_XHR(html: string) {
// We add these extra elements to ensure that the rest of the content is parsed as expected
// e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the
// `<head>` tag.
html = '<body><remove></remove>' + html + '</body>';
try {
html = encodeURI(html);
} catch (e) {
return null;
}
const xhr = new XMLHttpRequest();
xhr.responseType = 'document';
xhr.open('GET', 'data:text/html;charset=utf-8,' + html, false);
xhr.send(null);
const body: HTMLBodyElement = xhr.response.body;
body.removeChild(body.firstChild !);
return body;
}
/**
* Use DOMParser to create and fill an inert body element (on Firefox)
* See https://github.com/cure53/DOMPurify/releases/tag/0.6.7
*
*/
private getInertBodyElement_DOMParser(html: string) {
// We add these extra elements to ensure that the rest of the content is parsed as expected
// e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the
// `<head>` tag.
html = '<body><remove></remove>' + html + '</body>';
try {
const body = new (window as any)
.DOMParser()
.parseFromString(html, 'text/html')
.body as HTMLBodyElement;
body.removeChild(body.firstChild !);
return body;
} catch (e) {
return null;
}
}
/**
* Use an HTML5 `template` element, if supported, or an inert body element created via
* `createHtmlDocument` to create and fill an inert DOM element.
* This is the default sane strategy to use if the browser does not require one of the specialised
* strategies above.
*/
private getInertBodyElement_InertDocument(html: string) {
// Prefer using <template> element if supported.
const templateEl = this.DOM.createElement('template');
if ('content' in templateEl) {
this.DOM.setInnerHTML(templateEl, html);
return templateEl;
}
this.DOM.setInnerHTML(this.inertBodyElement, html);
// Support: IE 9-11 only
// strip custom-namespaced attributes on IE<=11
if (this.defaultDoc.documentMode) {
this.stripCustomNsAttrs(this.inertBodyElement);
}
return this.inertBodyElement;
}
/**
* When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1'
* attribute to declare ns1 namespace and prefixes the attribute with 'ns1' (e.g.
* 'ns1:xlink:foo').
*
* This is undesirable since we don't want to allow any of these custom attributes. This method
* strips them all.
*/
private stripCustomNsAttrs(el: Element) {
this.DOM.attributeMap(el).forEach((_, attrName) => {
if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) {
this.DOM.removeAttribute(el, attrName);
}
});
for (const n of this.DOM.childNodesAsList(el)) {
if (this.DOM.isElementNode(n)) this.stripCustomNsAttrs(n as Element);
}
}
}
/**
* We need to determine whether the DOMParser exists in the global context.
* The try-catch is because, on some browsers, trying to access this property
* on window can actually throw an error.
*
* @suppress {uselessCode}
*/
function isDOMParserAvailable() {
try {
return !!(window as any).DOMParser;
} catch (e) {
return false;
}
}

View File

@ -134,6 +134,32 @@ export function main() {
}
});
// See
// https://github.com/cure53/DOMPurify/blob/a992d3a75031cb8bb032e5ea8399ba972bdf9a65/src/purify.js#L439-L449
it('should not allow JavaScript execution when creating inert document', () => {
const output = sanitizeHtml(defaultDoc, '<svg><g onload="window.xxx = 100"></g></svg>');
const window = defaultDoc.defaultView;
if (window) {
expect(window.xxx).toBe(undefined);
window.xxx = undefined;
}
expect(output).toEqual('');
});
// See https://github.com/cure53/DOMPurify/releases/tag/0.6.7
it('should not allow JavaScript hidden in badly formed HTML to get through sanitization (Firefox bug)',
() => {
debugger;
expect(sanitizeHtml(
defaultDoc, '<svg><p><style><img src="</style><img src=x onerror=alert(1)//">'))
.toEqual(
isDOMParserAvailable() ?
// PlatformBrowser output
'<p>&lt;img src=&#34;<img src="x"></p>' :
// PlatformServer output
'<p><img src="&lt;/style&gt;&lt;img src=x onerror=alert(1)//"></p>');
});
if (browserDetection.isWebkit) {
it('should prevent mXSS attacks', function() {
expect(sanitizeHtml(defaultDoc, '<a href="&#x3000;javascript:alert(1)">CLICKME</a>'))
@ -142,3 +168,18 @@ export function main() {
}
});
}
/**
* We need to determine whether the DOMParser exists in the global context.
* The try-catch is because, on some browsers, trying to access this property
* on window can actually throw an error.
*
* @suppress {uselessCode}
*/
function isDOMParserAvailable() {
try {
return !!(window as any).DOMParser;
} catch (e) {
return false;
}
}

View File

@ -34,10 +34,11 @@
"webpack": "^2.2.1"
},
"scripts": {
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
"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": "webdriver-manager update --gecko false && tsc -p e2e",
"preprotractor": "tsc -p e2e",
"protractor": "protractor e2e/protractor.config.js"
}
}

View File

@ -17,7 +17,7 @@ function setEnvVar() {
if [[ ${print} == "print" ]]; then
echo ${name}=${value}
fi
export ${name}=${value}
export ${name}="${value}"
}
# use BASH_SOURCE so that we get the right path when this script is called AND source-d
@ -36,6 +36,10 @@ fi
setEnvVar NODE_VERSION 6.9.5
setEnvVar YARN_VERSION 1.0.2
# Pin to a Chromium version that does not cause the aio e2e tests to flake. (See https://github.com/angular/angular/pull/20403.)
# Revision 494239 (which was part of Chrome 62.0.3186.0) is the last version that does not cause flakes. (Latest revision checked: 508578)
setEnvVar CHROMIUM_VERSION 494239 # Chrome 62 linux stable, see https://www.chromium.org/developers/calendar
setEnvVar CHROMEDRIVER_VERSION_ARG "--versions.chrome 2.33"
setEnvVar SAUCE_CONNECT_VERSION 4.4.9
setEnvVar PROJECT_ROOT $(cd ${thisDir}/../..; pwd)
@ -101,6 +105,7 @@ if [[ ${TRAVIS:-} ]]; then
setEnvVar BROWSER_STACK_USERNAME angularteam1
# not using use setEnvVar so that we don't print the key
export BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB
setEnvVar CHROME_BIN ${HOME}/.chrome/chromium/chrome-linux/chrome
setEnvVar BROWSER_PROVIDER_READY_FILE /tmp/angular-build/browser-provider-tunnel-init.lock
fi

84
scripts/ci/install-chromium.sh Executable file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env bash
set -u -e -o pipefail
# Setup environment
readonly thisDir=$(cd $(dirname $0); pwd)
source ${thisDir}/_travis-fold.sh
# This script basically follows the instructions to download an old version of Chromium: https://www.chromium.org/getting-involved/download-chromium
# 1) It retrieves the current stable version number from https://www.chromium.org/developers/calendar (via the https://omahaproxy.appspot.com/all file), e.g. 359700 for Chromium 48.
# 2) It checks the Travis cache for this specific version
# 3) If not available, it downloads and caches it, using the "decrement commit number" trick.
#Build version read from the OmahaProxy CSV Viewer at https://www.chromium.org/developers/calendar
#Let's use the following version of Chromium, and inform about availability of newer build from https://omahaproxy.appspot.com/all
#
# CHROMIUM_VERSION <<< this variable is now set via env.sh
PLATFORM="$(uname -s)"
case "$PLATFORM" in
(Darwin)
ARCHITECTURE=Mac
DIST_FILE=chrome-mac.zip
;;
(Linux)
ARCHITECTURE=Linux_x64
DIST_FILE=chrome-linux.zip
;;
(*)
echo Unsupported platform $PLATFORM. Exiting ... >&2
exit 3
;;
esac
TMP=$(curl -s "https://omahaproxy.appspot.com/all") || true
oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1}
lines=( $TMP )
IFS=','
for line in "${lines[@]}"
do
lineArray=($line);
if [ "${lineArray[0]}" = "linux" ] && [ "${lineArray[1]}" = "stable" ] ; then
LATEST_CHROMIUM_VERSION="${lineArray[7]}"
fi
done
IFS="$oldIFS"
CHROMIUM_DIR=$HOME/.chrome/chromium
CHROMIUM_BIN=$CHROMIUM_DIR/chrome-linux/chrome
CHROMIUM_VERSION_FILE=$CHROMIUM_DIR/VERSION
EXISTING_VERSION=""
if [[ -f $CHROMIUM_VERSION_FILE && -x $CHROMIUM_BIN ]]; then
EXISTING_VERSION=`cat $CHROMIUM_VERSION_FILE`
echo Found cached Chromium version: ${EXISTING_VERSION}
fi
if [[ "$EXISTING_VERSION" != "$CHROMIUM_VERSION" ]]; then
echo Downloading Chromium version: ${CHROMIUM_VERSION}
rm -fR $CHROMIUM_DIR
mkdir -p $CHROMIUM_DIR
NEXT=$CHROMIUM_VERSION
FILE="chrome-linux.zip"
STATUS=404
while [[ $STATUS == 404 && $NEXT -ge 0 ]]
do
echo Fetch Chromium version: ${NEXT}
STATUS=$(curl "https://storage.googleapis.com/chromium-browser-snapshots/${ARCHITECTURE}/${NEXT}/${DIST_FILE}" -s -w %{http_code} --create-dirs -o $FILE) || true
NEXT=$[$NEXT-1]
done
unzip $FILE -d $CHROMIUM_DIR
rm $FILE
echo $CHROMIUM_VERSION > $CHROMIUM_VERSION_FILE
fi
if [[ "$CHROMIUM_VERSION" != "$LATEST_CHROMIUM_VERSION" ]]; then
echo "New version of Chromium available. Update install-chromium.sh with build number: ${LATEST_CHROMIUM_VERSION}"
fi

View File

@ -35,7 +35,7 @@ travisFoldEnd "install-yarn"
# Install all npm dependencies according to yarn.lock
travisFoldStart "yarn-install"
node tools/npm/check-node-modules --purge || yarn install --freeze-lockfile --non-interactive
(node tools/npm/check-node-modules --purge && yarn update-webdriver) || yarn install --frozen-lockfile --non-interactive
travisFoldEnd "yarn-install"
@ -64,11 +64,21 @@ if [[ ${TRAVIS} && ${CI_MODE} == "bazel" ]]; then
travisFoldEnd "bazel-install"
fi
# Start xvfb for local Chrome testing
if [[ ${TRAVIS} && (${CI_MODE} == "js" || ${CI_MODE} == "e2e" || ${CI_MODE} == "e2e_2" || ${CI_MODE} == "aio" || ${CI_MODE} == "aio_e2e") ]]; then
travisFoldStart "xvfb-start"
sh -e /etc/init.d/xvfb start
travisFoldEnd "xvfb-start"
# Install Chromium
if [[ ${TRAVIS} && ${CI_MODE} == "js" || ${CI_MODE} == "e2e" || ${CI_MODE} == "e2e_2" || ${CI_MODE} == "aio" || ${CI_MODE} == "aio_e2e" ]]; then
travisFoldStart "install-chromium"
(
${thisDir}/install-chromium.sh
# Start xvfb for local Chrome used for testing
if [[ ${TRAVIS} ]]; then
travisFoldStart "install-chromium.xvfb-start"
sh -e /etc/init.d/xvfb start
travisFoldEnd "install-chromium.xvfb-start"
fi
)
travisFoldEnd "install-chromium"
fi
@ -92,12 +102,6 @@ if [[ ${TRAVIS} && (${CI_MODE} == "browserstack_required" || ${CI_MODE} == "brow
fi
# Install Selenium WebDriver
travisFoldStart "webdriver-manager-update"
$(npm bin)/webdriver-manager update
travisFoldEnd "webdriver-manager-update"
# Install bower packages
travisFoldStart "bower-install"
$(npm bin)/bower install