From 491ba37461fe1d3d22e32c20f3d5043624040486 Mon Sep 17 00:00:00 2001 From: Mike Plummer Date: Wed, 18 Jan 2023 10:54:53 -0600 Subject: [PATCH 1/3] fix: Extend mock logger used in angular bootstrap * Add missing logger functions that caused failure * Proxy mock logger to debug so output isn't swallowed when troubleshooting * Minor test performance improvement --- .../cypress/e2e/angular.cy.ts | 133 ++++++++++-------- .../src/helpers/angularHandler.ts | 27 ++-- .../angular/src/app/app.component.cy.ts | 2 +- 3 files changed, 98 insertions(+), 64 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..7927fffe5d2a 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 } 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 @@ -203,14 +205,23 @@ export async function getAngularJson (projectRoot: string): Promise return JSON.parse(angularJson) } +function buildLogger (name: string = ''): logging.Logger { + const debugLogger = debugLib(`${debugPrefix}${name ? `:${name}` : ''}`) + + return { + name, + log: debugLogger, + debug: debugLogger, + info: debugLogger, + warn: debugLogger, + error: debugLogger, + fatal: debugLogger, + createChild: buildLogger, + } as unknown as logging.Logger +} + function createFakeContext (projectRoot: string, defaultProjectConfig: Cypress.AngularDevServerProjectConfig) { - const logger = { - createChild: () => { - return { - warn: () => {}, - } - }, - } + const logger: logging.LoggerApi = buildLogger() const context = { target: { 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 }) }) From 9873fa4a6609306b496f15c20de025845efaf91a Mon Sep 17 00:00:00 2001 From: Mike Plummer Date: Thu, 19 Jan 2023 08:50:52 -0600 Subject: [PATCH 2/3] Build actual logger and proxy to debug logger --- .../src/helpers/angularHandler.ts | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/npm/webpack-dev-server/src/helpers/angularHandler.ts b/npm/webpack-dev-server/src/helpers/angularHandler.ts index 7927fffe5d2a..65aab67bec5b 100644 --- a/npm/webpack-dev-server/src/helpers/angularHandler.ts +++ b/npm/webpack-dev-server/src/helpers/angularHandler.ts @@ -6,7 +6,7 @@ import type { PresetHandlerResult, WebpackDevServerConfig } from '../devServer' import { dynamicAbsoluteImport, dynamicImport } from '../dynamic-import' import { sourceDefaultWebpackDependencies } from './sourceRelativeWebpackModules' import debugLib from 'debug' -import type { logging } from '@angular-devkit/core' +import { logging } from '@angular-devkit/core' const debugPrefix = 'cypress:webpack-dev-server:angularHandler' const debug = debugLib(debugPrefix) @@ -205,23 +205,13 @@ export async function getAngularJson (projectRoot: string): Promise return JSON.parse(angularJson) } -function buildLogger (name: string = ''): logging.Logger { - const debugLogger = debugLib(`${debugPrefix}${name ? `:${name}` : ''}`) - - return { - name, - log: debugLogger, - debug: debugLogger, - info: debugLogger, - warn: debugLogger, - error: debugLogger, - fatal: debugLogger, - createChild: buildLogger, - } as unknown as logging.Logger -} - function createFakeContext (projectRoot: string, defaultProjectConfig: Cypress.AngularDevServerProjectConfig) { - const logger: logging.LoggerApi = buildLogger() + const logger = new logging.Logger(debugPrefix) + + // Proxy all logging calls through to the debug logger + logger.forEach((value: logging.LogEntry) => { + debug(JSON.stringify(value)) + }) const context = { target: { From 3c62db12f7ccbf3ab8e07d7e8d7fa2a6c25a9e48 Mon Sep 17 00:00:00 2001 From: Mike Plummer Date: Thu, 19 Jan 2023 12:37:17 -0600 Subject: [PATCH 3/3] Fix import of Angular logging dependency --- .../src/helpers/angularHandler.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/npm/webpack-dev-server/src/helpers/angularHandler.ts b/npm/webpack-dev-server/src/helpers/angularHandler.ts index 65aab67bec5b..82c197569a29 100644 --- a/npm/webpack-dev-server/src/helpers/angularHandler.ts +++ b/npm/webpack-dev-server/src/helpers/angularHandler.ts @@ -6,7 +6,7 @@ import type { PresetHandlerResult, WebpackDevServerConfig } from '../devServer' import { dynamicAbsoluteImport, dynamicImport } from '../dynamic-import' import { sourceDefaultWebpackDependencies } from './sourceRelativeWebpackModules' import debugLib from 'debug' -import { logging } from '@angular-devkit/core' +import type { logging as AngularLogging } from '@angular-devkit/core' const debugPrefix = 'cypress:webpack-dev-server:angularHandler' const debug = debugLib(debugPrefix) @@ -168,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?`) } })) @@ -188,6 +190,7 @@ export async function getAngularCliModules (projectRoot: string) { generateBrowserWebpackConfigFromContext, getCommonConfig, getStylesConfig, + logging, } } @@ -205,11 +208,11 @@ export async function getAngularJson (projectRoot: string): Promise return JSON.parse(angularJson) } -function createFakeContext (projectRoot: string, defaultProjectConfig: Cypress.AngularDevServerProjectConfig) { +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: logging.LogEntry) => { + logger.forEach((value: AngularLogging.LogEntry) => { debug(JSON.stringify(value)) }) @@ -240,6 +243,7 @@ async function getAngularCliWebpackConfig (devServerConfig: AngularWebpackDevSer generateBrowserWebpackConfigFromContext, getCommonConfig, getStylesConfig, + logging, } = await getAngularCliModules(projectRoot) // normalize @@ -249,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,