From fdd402fee2d3eb46150e8e785556607045016aa8 Mon Sep 17 00:00:00 2001 From: Mike Plummer Date: Thu, 19 Jan 2023 16:01:16 -0600 Subject: [PATCH] fix: Extend mock logger used in angular bootstrap (#25500) --- .../cypress/e2e/angular.cy.ts | 133 ++++++++++-------- .../src/helpers/angularHandler.ts | 27 ++-- .../angular/src/app/app.component.cy.ts | 2 +- 3 files changed, 95 insertions(+), 67 deletions(-) diff --git a/npm/webpack-dev-server/cypress/e2e/angular.cy.ts b/npm/webpack-dev-server/cypress/e2e/angular.cy.ts index c0454de4dcf0..a67c61df8b5c 100644 --- a/npm/webpack-dev-server/cypress/e2e/angular.cy.ts +++ b/npm/webpack-dev-server/cypress/e2e/angular.cy.ts @@ -1,4 +1,4 @@ -// +/// /// import type { ProjectFixtureDir } from '@tooling/system-tests/lib/fixtureDirs' @@ -12,83 +12,106 @@ for (const project of WEBPACK_REACT) { continue } - describe(`Working with ${project}`, () => { + context(project, () => { beforeEach(() => { cy.scaffoldProject(project) cy.openProject(project) - cy.startAppServer('component') }) - it('should mount a passing test', () => { - cy.visitApp() - cy.contains('app.component.cy.ts').click() - cy.waitForSpecToFinish({ passCount: 1 }, 60000) + describe('configuration handling', () => { + if (!['angular-13', 'angular-14'].includes(project)) { + it('should initialize with unsupported browserslist entries', () => { + // Create .browerslistrc that requests support for ES5 + // Support was dropped in Angular CLI v15 so this should generate a warning message in that version and beyond + cy.withCtx(async (ctx) => { + await ctx.actions.file.writeFileInProject( + ctx.path.resolve('.browserslistrc'), + 'IE 11', + ) + }) + + cy.startAppServer('component') + cy.visitApp() + }) + } + }) - cy.get('li.command').first().within(() => { - cy.get('.command-method').should('contain', 'mount') - cy.get('.command-message').should('contain', 'AppComponent') + describe('test behaviors', () => { + beforeEach(() => { + cy.startAppServer('component') }) - }) - it('should live-reload on src changes', () => { - cy.visitApp() - cy.contains('app.component.cy.ts').click() - cy.waitForSpecToFinish({ passCount: 1 }, 60000) + it('should mount a passing test', () => { + cy.visitApp() + cy.contains('app.component.cy.ts').click() + cy.waitForSpecToFinish({ passCount: 1 }, 60000) - cy.withCtx(async (ctx) => { - await ctx.actions.file.writeFileInProject( - ctx.path.join('src', 'app', 'app.component.html'), - (await ctx.file.readFileInProject(ctx.path.join('src', 'app', 'app.component.html'))).replace('Hello World', 'Hello Cypress'), - ) + cy.get('li.command').first().within(() => { + cy.get('.command-method').should('contain', 'mount') + cy.get('.command-message').should('contain', 'AppComponent') + }) }) - cy.waitForSpecToFinish({ failCount: 1 }, 60000) + it('should live-reload on src changes', () => { + cy.visitApp() + cy.contains('app.component.cy.ts').click() + cy.waitForSpecToFinish({ passCount: 1 }, 60000) + + cy.withCtx(async (ctx) => { + await ctx.actions.file.writeFileInProject( + ctx.path.join('src', 'app', 'app.component.html'), + (await ctx.file.readFileInProject(ctx.path.join('src', 'app', 'app.component.html'))).replace('Hello World', 'Hello Cypress'), + ) + }) + + cy.waitForSpecToFinish({ failCount: 1 }, 60000) - cy.withCtx(async (ctx) => { - await ctx.actions.file.writeFileInProject( - ctx.path.join('src', 'app', 'app.component.html'), - (await ctx.file.readFileInProject(ctx.path.join('src', 'app', 'app.component.html'))).replace('Hello Cypress', 'Hello World'), - ) + cy.withCtx(async (ctx) => { + await ctx.actions.file.writeFileInProject( + ctx.path.join('src', 'app', 'app.component.html'), + (await ctx.file.readFileInProject(ctx.path.join('src', 'app', 'app.component.html'))).replace('Hello Cypress', 'Hello World'), + ) + }) + + cy.waitForSpecToFinish({ passCount: 1 }, 60000) }) - cy.waitForSpecToFinish({ passCount: 1 }, 60000) - }) + it('should show compilation errors on src changes', () => { + cy.visitApp() - it('should show compilation errors on src changes', () => { - cy.visitApp() + cy.contains('app.component.cy.ts').click() + cy.waitForSpecToFinish({ passCount: 1 }, 60000) - cy.contains('app.component.cy.ts').click() - cy.waitForSpecToFinish({ passCount: 1 }, 60000) + // Create compilation error + cy.withCtx(async (ctx) => { + const componentFilePath = ctx.path.join('src', 'app', 'app.component.ts') - // Create compilation error - cy.withCtx(async (ctx) => { - const componentFilePath = ctx.path.join('src', 'app', 'app.component.ts') + await ctx.actions.file.writeFileInProject( + componentFilePath, + (await ctx.file.readFileInProject(componentFilePath)).replace('class', 'classaaaaa'), + ) + }) - await ctx.actions.file.writeFileInProject( - componentFilePath, - (await ctx.file.readFileInProject(componentFilePath)).replace('class', 'classaaaaa'), - ) + // The test should fail and the stack trace should appear in the command log + cy.waitForSpecToFinish({ failCount: 1 }, 60000) + cy.contains('The following error originated from your test code, not from Cypress.').should('exist') + cy.get('.test-err-code-frame').should('be.visible') }) - // The test should fail and the stack trace should appear in the command log - cy.waitForSpecToFinish({ failCount: 1 }, 60000) - cy.contains('The following error originated from your test code, not from Cypress.').should('exist') - cy.get('.test-err-code-frame').should('be.visible') - }) + // TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23455 + it('should detect new spec', { retries: 15 }, () => { + cy.visitApp() - // TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23455 - it('should detect new spec', { retries: 15 }, () => { - cy.visitApp() + cy.withCtx(async (ctx) => { + await ctx.actions.file.writeFileInProject( + ctx.path.join('src', 'app', 'new.component.cy.ts'), + await ctx.file.readFileInProject(ctx.path.join('src', 'app', 'app.component.cy.ts')), + ) + }) - cy.withCtx(async (ctx) => { - await ctx.actions.file.writeFileInProject( - ctx.path.join('src', 'app', 'new.component.cy.ts'), - await ctx.file.readFileInProject(ctx.path.join('src', 'app', 'app.component.cy.ts')), - ) + cy.contains('new.component.cy.ts').click() + cy.waitForSpecToFinish({ passCount: 1 }, 60000) }) - - cy.contains('new.component.cy.ts').click() - cy.waitForSpecToFinish({ passCount: 1 }, 60000) }) }) } diff --git a/npm/webpack-dev-server/src/helpers/angularHandler.ts b/npm/webpack-dev-server/src/helpers/angularHandler.ts index 1135be85d496..82c197569a29 100644 --- a/npm/webpack-dev-server/src/helpers/angularHandler.ts +++ b/npm/webpack-dev-server/src/helpers/angularHandler.ts @@ -6,8 +6,10 @@ import type { PresetHandlerResult, WebpackDevServerConfig } from '../devServer' import { dynamicAbsoluteImport, dynamicImport } from '../dynamic-import' import { sourceDefaultWebpackDependencies } from './sourceRelativeWebpackModules' import debugLib from 'debug' +import type { logging as AngularLogging } from '@angular-devkit/core' -const debug = debugLib('cypress:webpack-dev-server:angularHandler') +const debugPrefix = 'cypress:webpack-dev-server:angularHandler' +const debug = debugLib(debugPrefix) export type BuildOptions = Record @@ -166,19 +168,21 @@ export async function getAngularCliModules (projectRoot: string) { '@angular-devkit/build-angular/src/utils/webpack-browser-config.js', '@angular-devkit/build-angular/src/webpack/configs/common.js', '@angular-devkit/build-angular/src/webpack/configs/styles.js', + '@angular-devkit/core/src/index.js', ] as const const [ { generateBrowserWebpackConfigFromContext }, { getCommonConfig }, { getStylesConfig }, + { logging }, ] = await Promise.all(angularCLiModules.map((dep) => { try { const depPath = require.resolve(dep, { paths: [projectRoot] }) return dynamicAbsoluteImport(depPath) } catch (e) { - throw new Error(`Could not resolve "${dep}". Do you have "@angular-devkit/build-angular" installed?`) + throw new Error(`Could not resolve "${dep}". Do you have "@angular-devkit/build-angular" and "@angular-devkit/core" installed?`) } })) @@ -186,6 +190,7 @@ export async function getAngularCliModules (projectRoot: string) { generateBrowserWebpackConfigFromContext, getCommonConfig, getStylesConfig, + logging, } } @@ -203,14 +208,13 @@ export async function getAngularJson (projectRoot: string): Promise return JSON.parse(angularJson) } -function createFakeContext (projectRoot: string, defaultProjectConfig: Cypress.AngularDevServerProjectConfig) { - const logger = { - createChild: () => { - return { - warn: () => {}, - } - }, - } +function createFakeContext (projectRoot: string, defaultProjectConfig: Cypress.AngularDevServerProjectConfig, logging: typeof AngularLogging) { + const logger = new logging.Logger(debugPrefix) + + // Proxy all logging calls through to the debug logger + logger.forEach((value: AngularLogging.LogEntry) => { + debug(JSON.stringify(value)) + }) const context = { target: { @@ -239,6 +243,7 @@ async function getAngularCliWebpackConfig (devServerConfig: AngularWebpackDevSer generateBrowserWebpackConfigFromContext, getCommonConfig, getStylesConfig, + logging, } = await getAngularCliModules(projectRoot) // normalize @@ -248,7 +253,7 @@ async function getAngularCliWebpackConfig (devServerConfig: AngularWebpackDevSer const buildOptions = getAngularBuildOptions(projectConfig.buildOptions, tsConfig) - const context = createFakeContext(projectRoot, projectConfig) + const context = createFakeContext(projectRoot, projectConfig, logging) const { config } = await generateBrowserWebpackConfigFromContext( buildOptions, diff --git a/system-tests/project-fixtures/angular/src/app/app.component.cy.ts b/system-tests/project-fixtures/angular/src/app/app.component.cy.ts index 8356874b413e..aa1f8e4481f2 100644 --- a/system-tests/project-fixtures/angular/src/app/app.component.cy.ts +++ b/system-tests/project-fixtures/angular/src/app/app.component.cy.ts @@ -3,5 +3,5 @@ import { AppComponent } from './app.component' it('should', () => { cy.mount(AppComponent) - cy.get('h1').contains('Hello World') + cy.get('h1').contains('Hello World', { timeout: 250 }) })