From 76539db5dbb2a61939829c1afde61a5bf4d9f738 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Tue, 26 Jul 2022 14:08:07 +0000 Subject: [PATCH] test(@angular-devkit/build-angular): re-write live-reload tests to use new test harness. With this change we replace the live-reload proxy tests to use the new test harness. This is an effort to reduce the flakiness of these tests. (cherry picked from commit cdbb48f7b2998dd46bd341dd95cec596b96b8fac) --- .../serve-live-reload-proxies_spec.ts} | 236 ++++++++++-------- 1 file changed, 135 insertions(+), 101 deletions(-) rename packages/angular_devkit/build_angular/src/builders/dev-server/{specs/live-reload_spec.ts => tests/behavior/serve-live-reload-proxies_spec.ts} (59%) diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/specs/live-reload_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/serve-live-reload-proxies_spec.ts similarity index 59% rename from packages/angular_devkit/build_angular/src/builders/dev-server/specs/live-reload_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/serve-live-reload-proxies_spec.ts index 857c6bff36bd..ae19605a0edd 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/specs/live-reload_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/serve-live-reload-proxies_spec.ts @@ -7,14 +7,20 @@ */ /* eslint-disable import/no-extraneous-dependencies */ -import { Architect, BuilderRun } from '@angular-devkit/architect'; import { tags } from '@angular-devkit/core'; import { createServer } from 'http'; import { createProxyServer } from 'http-proxy'; import { AddressInfo } from 'net'; import puppeteer, { Browser, Page } from 'puppeteer'; -import { debounceTime, switchMap, take } from 'rxjs/operators'; -import { createArchitect, host } from '../../../testing/test-utils'; +import { count, debounceTime, finalize, switchMap, take, timeout } from 'rxjs/operators'; +import { serveWebpackBrowser } from '../../index'; +import { + BASE_OPTIONS, + BUILD_TIMEOUT, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; // eslint-disable-next-line @typescript-eslint/no-explicit-any declare const document: any; @@ -132,135 +138,159 @@ async function goToPageAndWaitForWS(page: Page, url: string): Promise { }), page.goto(url), ]); + await client.detach(); } -describe('Dev Server Builder live-reload', () => { - const target = { project: 'app', target: 'serve' }; - // TODO: check if the below is still true. - // Avoid using port `0` as these tests will behave differrently and tests will pass when they shouldn't. - // Port 0 and host 0.0.0.0 have special meaning in dev-server. - const overrides = { hmr: false, watch: true, port: 4202, liveReload: true }; - let architect: Architect; - let browser: Browser; - let page: Page; - let runs: BuilderRun[]; - - beforeAll(async () => { - browser = await puppeteer.launch({ - // MacOSX users need to set the local binary manually because Chrome has lib files with - // spaces in them which Bazel does not support in runfiles - // See: https://github.com/angular/angular-cli/pull/17624 - // eslint-disable-next-line max-len - // executablePath: '/Users//git/angular-cli/node_modules/puppeteer/.local-chromium/mac-818858/chrome-mac/Chromium.app/Contents/MacOS/Chromium', - ignoreHTTPSErrors: true, - args: ['--no-sandbox', '--disable-gpu'], +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('Behavior: "Dev-server builder live-reload with proxies"', () => { + let browser: Browser; + let page: Page; + + const SERVE_OPTIONS = Object.freeze({ + ...BASE_OPTIONS, + hmr: false, + watch: true, + liveReload: true, }); - }); - afterAll(async () => { - await browser.close(); - }); + beforeAll(async () => { + browser = await puppeteer.launch({ + // MacOSX users need to set the local binary manually because Chrome has lib files with + // spaces in them which Bazel does not support in runfiles + // See: https://github.com/angular/angular-cli/pull/17624 + // eslint-disable-next-line max-len + // executablePath: '/Users//git/angular-cli/node_modules/puppeteer/.local-chromium/mac-818858/chrome-mac/Chromium.app/Contents/MacOS/Chromium', + ignoreHTTPSErrors: true, + args: ['--no-sandbox', '--disable-gpu'], + }); + }); - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; + afterAll(async () => { + await browser.close(); + }); + + beforeEach(async () => { + setupBrowserTarget(harness, { + polyfills: 'src/polyfills.ts', + }); - host.writeMultipleFiles({ - 'src/app/app.component.html': ` -

{{ title }}

- `, + page = await browser.newPage(); }); - runs = []; - page = await browser.newPage(); - }); + afterEach(async () => { + await page.close(); + }); - afterEach(async () => { - await host.restore().toPromise(); - await page.close(); - await Promise.all(runs.map((r) => r.stop())); - }); + it('works without proxy', async () => { + harness.useTarget('serve', { + ...SERVE_OPTIONS, + }); - it('works without proxy', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); - - await run.output - .pipe( - debounceTime(1000), - switchMap(async (buildEvent, buildCount) => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - switch (buildCount) { - case 0: - await goToPageAndWaitForWS(page, url); - host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`); - break; - case 1: - const innerText = await page.evaluate(() => document.querySelector('p').innerText); - expect(innerText).toBe('app-live-reload'); - break; - } - }), - take(2), - ) - .toPromise(); - }); + await harness.writeFile('src/app/app.component.html', '

{{ title }}

'); - it('works without http -> http proxy', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); + const buildCount = await harness + .execute() + .pipe( + debounceTime(1000), + timeout(BUILD_TIMEOUT * 2), + switchMap(async ({ result }, index) => { + expect(result?.success).toBeTrue(); + if (typeof result?.baseUrl !== 'string') { + throw new Error('Expected "baseUrl" to be a string.'); + } - let proxy: ProxyInstance | undefined; - let buildCount = 0; - try { - await run.output + switch (index) { + case 0: + await goToPageAndWaitForWS(page, result.baseUrl); + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace(`'app'`, `'app-live-reload'`), + ); + break; + case 1: + const innerText = await page.evaluate(() => document.querySelector('p').innerText); + expect(innerText).toBe('app-live-reload'); + break; + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + + it('works without http -> http proxy', async () => { + harness.useTarget('serve', { + ...SERVE_OPTIONS, + }); + + await harness.writeFile('src/app/app.component.html', '

{{ title }}

'); + + let proxy: ProxyInstance | undefined; + const buildCount = await harness + .execute() .pipe( debounceTime(1000), - switchMap(async (buildEvent) => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - switch (buildCount) { + timeout(BUILD_TIMEOUT * 2), + switchMap(async ({ result }, index) => { + expect(result?.success).toBeTrue(); + if (typeof result?.baseUrl !== 'string') { + throw new Error('Expected "baseUrl" to be a string.'); + } + + switch (index) { case 0: - proxy = await createProxy(url, false); + proxy = await createProxy(result.baseUrl, false); await goToPageAndWaitForWS(page, proxy.url); - host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`); + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace(`'app'`, `'app-live-reload'`), + ); break; case 1: const innerText = await page.evaluate(() => document.querySelector('p').innerText); expect(innerText).toBe('app-live-reload'); break; } - - buildCount++; }), take(2), + count(), + finalize(() => { + proxy?.server.close(); + }), ) .toPromise(); - } finally { - proxy?.server.close(); - } - }); - it('works without https -> http proxy', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); + expect(buildCount).toBe(2); + }); + + it('works without https -> http proxy', async () => { + harness.useTarget('serve', { + ...SERVE_OPTIONS, + }); - let proxy: ProxyInstance | undefined; + await harness.writeFile('src/app/app.component.html', '

{{ title }}

'); - try { - await run.output + let proxy: ProxyInstance | undefined; + const buildCount = await harness + .execute() .pipe( debounceTime(1000), - switchMap(async (buildEvent, buildCount) => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - switch (buildCount) { + timeout(BUILD_TIMEOUT * 2), + switchMap(async ({ result }, index) => { + expect(result?.success).toBeTrue(); + if (typeof result?.baseUrl !== 'string') { + throw new Error('Expected "baseUrl" to be a string.'); + } + + switch (index) { case 0: - proxy = await createProxy(url, true); + proxy = await createProxy(result.baseUrl, true); await goToPageAndWaitForWS(page, proxy.url); - host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`); + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace(`'app'`, `'app-live-reload'`), + ); break; case 1: const innerText = await page.evaluate(() => document.querySelector('p').innerText); @@ -269,10 +299,14 @@ describe('Dev Server Builder live-reload', () => { } }), take(2), + count(), + finalize(() => { + proxy?.server.close(); + }), ) .toPromise(); - } finally { - proxy?.server.close(); - } + + expect(buildCount).toBe(2); + }); }); });