diff --git a/src/universal-app/BUILD.bazel b/src/universal-app/BUILD.bazel index ae0c52b272fa..62f8afc30b51 100644 --- a/src/universal-app/BUILD.bazel +++ b/src/universal-app/BUILD.bazel @@ -4,7 +4,7 @@ load("//src/cdk:config.bzl", "CDK_TARGETS") load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_TARGETS") load("//src/material:config.bzl", "MATERIAL_TARGETS") load("//src/material-experimental:config.bzl", "MATERIAL_EXPERIMENTAL_TARGETS") -load("//tools:defaults.bzl", "devmode_esbuild", "http_server", "ng_module", "sass_binary", "ts_library") +load("//tools:defaults.bzl", "devmode_esbuild", "http_server", "ng_e2e_test_library", "ng_module", "protractor_web_test_suite", "sass_binary", "ts_library") load("//tools/angular:index.bzl", "LINKER_PROCESSED_FW_PACKAGES") package(default_visibility = ["//visibility:public"]) @@ -127,6 +127,7 @@ http_server( name = "server", srcs = [ ":debug_prerender_bin", + "@npm//zone.js", ], additional_root_paths = [ "npm/node_modules", @@ -137,3 +138,19 @@ http_server( ":styles_scss", ], ) + +ng_e2e_test_library( + name = "hydration_e2e_tests_sources", + srcs = ["hydration.e2e.spec.ts"], +) + +protractor_web_test_suite( + name = "hydration_e2e_tests", + configuration = ":protractor.conf.js", + on_prepare = ":start-devserver.js", + server = ":server", + tags = ["e2e"], + deps = [ + ":hydration_e2e_tests_sources", + ], +) diff --git a/src/universal-app/hydration.e2e.spec.ts b/src/universal-app/hydration.e2e.spec.ts new file mode 100644 index 000000000000..974b1fcd289d --- /dev/null +++ b/src/universal-app/hydration.e2e.spec.ts @@ -0,0 +1,33 @@ +import {browser, by, element, ExpectedConditions} from 'protractor'; + +describe('hydration e2e', () => { + beforeEach(async () => { + await browser.waitForAngularEnabled(false); + await browser.get('/'); + await browser.wait(ExpectedConditions.presenceOf(element(by.css('.render-marker'))), 5000); + }); + + it('should enable hydration', async () => { + const hydrationState = await getHydrationState(); + const logs = await browser.manage().logs().get('browser'); + + expect(hydrationState.hydratedComponents).toBeGreaterThan(0); + expect(logs.map(log => log.message).filter(msg => msg.includes('NG0500'))).toEqual([]); + }); + + it('should not skip hydration on any components', async () => { + const hydrationState = await getHydrationState(); + expect(hydrationState.componentsSkippedHydration).toBe(0); + }); +}); + +/** Gets the hydration state from the current app. */ +async function getHydrationState() { + return browser.executeScript<{ + hydratedComponents: number; + componentsSkippedHydration: number; + }>(() => ({ + hydratedComponents: (window as any).ngDevMode.hydratedComponents, + componentsSkippedHydration: (window as any).ngDevMode.componentsSkippedHydration, + })); +} diff --git a/src/universal-app/kitchen-sink/kitchen-sink.html b/src/universal-app/kitchen-sink/kitchen-sink.html index a425b1e1d98d..eb4aa95f1502 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.html +++ b/src/universal-app/kitchen-sink/kitchen-sink.html @@ -606,3 +606,6 @@

Google Map

+ + +
diff --git a/src/universal-app/protractor.conf.js b/src/universal-app/protractor.conf.js new file mode 100644 index 000000000000..e701601a0fb7 --- /dev/null +++ b/src/universal-app/protractor.conf.js @@ -0,0 +1,12 @@ +exports.config = { + useAllAngular2AppRoots: true, + allScriptsTimeout: 120000, + getPageTimeout: 120000, + jasmineNodeOpts: { + defaultTimeoutInterval: 120000, + }, + + // Since we want to use async/await we don't want to mix up with selenium's promise + // manager. In order to enforce this, we disable the promise manager. + SELENIUM_PROMISE_MANAGER: false, +}; diff --git a/src/universal-app/start-devserver.js b/src/universal-app/start-devserver.js new file mode 100644 index 000000000000..c4d521edea41 --- /dev/null +++ b/src/universal-app/start-devserver.js @@ -0,0 +1,21 @@ +const protractor = require('protractor'); + +// Note: We need to specify an explicit file extension here because otherwise +// the Bazel-patched NodeJS module resolution would resolve to the `.mjs` file +// in non-sandbox environments (as usually with Bazel and Windows). +const utils = require('@bazel/protractor/protractor-utils.js'); + +/** + * Called by Protractor before starting any tests. This is script is responsible for + * starting up the devserver and updating the Protractor base URL to the proper port. + */ +module.exports = async function (config) { + const {port} = await utils.runServer(config.workspace, config.server, '--port', []); + const baseUrl = `http://localhost:${port}`; + const processedConfig = await protractor.browser.getProcessedConfig(); + + // Update the protractor "baseUrl" to match the new random TCP port. We need random TCP ports + // because otherwise Bazel could not execute protractor tests concurrently. + protractor.browser.baseUrl = baseUrl; + processedConfig.baseUrl = baseUrl; +};