Skip to content

Commit

Permalink
feat: warn when detecting unsupported dependencies for component test…
Browse files Browse the repository at this point in the history
…ing (#22964)

* wip: basic implementation

* update dependencies to have maxVersion

* handle promises correctly

* fix test

* update test project and styling

* only check for CT deps in CT

* install required deps

* revert

* rework detection and extend tests

* remove unused code

* remove more code

Co-authored-by: Zachary Williams <[email protected]>
  • Loading branch information
lmiller1990 and ZachJW34 authored Aug 1, 2022
1 parent 2612219 commit cbb5e35
Show file tree
Hide file tree
Showing 22 changed files with 21,141 additions and 14 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ system-tests/projects/create-react-app-custom-index-html

system-tests/projects/vueclivue2-unconfigured/**/*
system-tests/projects/vueclivue2-configured/**/*
system-tests/projects/outdated-deps-vuecli3/**/*

system-tests/projects/vueclivue3-unconfigured/**/*
system-tests/projects/vueclivue3-configured/**/*
Expand Down
79 changes: 78 additions & 1 deletion packages/data-context/src/data/ProjectConfigManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CypressEnv } from './CypressEnv'
import { autoBindDebug } from '../util/autoBindDebug'
import type { EventRegistrar } from './EventRegistrar'
import type { DataContext } from '../DataContext'
import { DependencyToInstall, inPkgJson, WIZARD_BUNDLERS, WIZARD_DEPENDENCIES, WIZARD_FRAMEWORKS } from '@packages/scaffold-config'

const debug = debugLib(`cypress:lifecycle:ProjectConfigManager`)

Expand Down Expand Up @@ -153,8 +154,84 @@ export class ProjectConfigManager {
if (this._registeredEventsTarget && this._testingType !== this._registeredEventsTarget) {
this.options.refreshLifecycle().catch(this.onLoadError)
} else if (this._eventsIpc && !this._registeredEventsTarget && this._cachedLoadConfig) {
this.setupNodeEvents(this._cachedLoadConfig).catch(this.onLoadError)
this.setupNodeEvents(this._cachedLoadConfig)
.then(() => {
if (this._testingType === 'component') {
this.checkDependenciesForComponentTesting()
}
})
.catch(this.onLoadError)
}
}

checkDependenciesForComponentTesting () {
// if it's a function, for example, the user is created their own dev server,
// and not using one of our presets. Assume they know what they are doing and
// what dependencies they require.
if (typeof this._cachedLoadConfig?.initialConfig?.component?.devServer !== 'object') {
return
}

const devServerOptions = this._cachedLoadConfig.initialConfig.component.devServer

const bundler = WIZARD_BUNDLERS.find((x) => x.type === devServerOptions.bundler)

// Use a map since sometimes the same dependency can appear in `bundler` and `framework`,
// for example webpack appears in both `bundler: 'webpack', framework: 'react-scripts'`
const unsupportedDeps = new Map<DependencyToInstall['dependency']['type'], DependencyToInstall>()

if (!bundler) {
return
}

const result = inPkgJson(bundler, this.options.projectRoot)

if (!result.satisfied) {
unsupportedDeps.set(result.dependency.type, result)
}

const isFrameworkSatisfied = (bundler: typeof WIZARD_BUNDLERS[number], framework: typeof WIZARD_FRAMEWORKS[number]) => {
for (const dep of framework.dependencies(bundler.type, this.options.projectRoot)) {
const res = inPkgJson(dep.dependency, this.options.projectRoot)

if (!res.satisfied) {
return false
}
}

return true
}

const frameworks = WIZARD_FRAMEWORKS.filter((x) => x.configFramework === devServerOptions.framework)

const mismatchedFrameworkDeps = new Map<typeof WIZARD_DEPENDENCIES[number]['type'], DependencyToInstall>()

let isSatisfied = false

for (const framework of frameworks) {
if (isFrameworkSatisfied(bundler, framework)) {
isSatisfied = true
break
} else {
for (const dep of framework.dependencies(bundler.type, this.options.projectRoot)) {
mismatchedFrameworkDeps.set(dep.dependency.type, dep)
}
}
}

if (!isSatisfied) {
for (const dep of Array.from(mismatchedFrameworkDeps.values())) {
if (!dep.satisfied) {
unsupportedDeps.set(dep.dependency.type, dep)
}
}
}

if (unsupportedDeps.size === 0) {
return
}

this.options.ctx.onWarning(getError('COMPONENT_TESTING_MISMATCHED_DEPENDENCIES', Array.from(unsupportedDeps.values())))
}

private async setupNodeEvents (loadConfigReply: LoadConfigReply): Promise<void> {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions packages/errors/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { BreakingErrResult } from '@packages/config'
import { humanTime, logError, parseResolvedPattern, pluralize } from './errorUtils'
import { errPartial, errTemplate, fmt, theme, PartialErr } from './errTemplate'
import { stackWithoutMessage } from './stackUtils'
import type { DependencyToInstall } from '@packages/scaffold-config'
import type { ClonedError, ConfigValidationFailureInfo, CypressError, ErrTemplateResult, ErrorLike } from './errorTypes'

const ansi_up = new AU()
Expand Down Expand Up @@ -1561,6 +1562,24 @@ export const AllCypressErrors = {
https://on.cypress.io/configuration
`
},

COMPONENT_TESTING_MISMATCHED_DEPENDENCIES: (dependencies: DependencyToInstall[]) => {
const deps = dependencies.map<string>((dep) => {
if (dep.detectedVersion) {
return `\`${dep.dependency.installer}\`. Expected ${dep.dependency.minVersion}, found ${dep.detectedVersion}.`
}

return `\`${dep.dependency.installer}\`. Expected ${dep.dependency.minVersion} but dependency was not found.`
})

return errTemplate`
We detected that you have versions of dependencies that are not officially supported:
${fmt.listItems(deps, { prefix: ' - ' })}
If you're experiencing problems, downgrade dependencies and restart Cypress.
`
},
} as const

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
22 changes: 22 additions & 0 deletions packages/errors/test/unit/visualSnapshotErrors_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1172,5 +1172,27 @@ describe('visual error templates', () => {
default: ['component'],
}
},

COMPONENT_TESTING_MISMATCHED_DEPENDENCIES: () => {
return {
default: [
[
{
dependency: {
type: 'vite',
name: 'Vite',
package: 'vite',
installer: 'vite',
description: 'Vite is dev server that serves your source files over native ES modules',
minVersion: '>=2.0.0',
},
satisfied: false,
detectedVersion: '1.0.0',
loc: null,
},
],
],
}
},
})
})
12 changes: 12 additions & 0 deletions packages/frontend-shared/src/warning/Warning.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
>
<div
ref="markdownTarget"
class="warning-markdown"
v-html="markdown"
/>
<Button
Expand Down Expand Up @@ -68,3 +69,14 @@ let message = computed(() => {
const { markdown } = useMarkdown(markdownTarget, message.value, { classes: { code: ['bg-warning-200'] } })
</script>

<style lang="scss">
// Add some extra margin to the <ul>
// TODO: ideally move this into `frontend-shared/src/composables/useMarkdown`
// It doesn't get applied when added there due to conflicting with other, higher priority rules.
.warning-markdown {
ul {
@apply ml-16px mb-16px;
}
}
</style>
1 change: 1 addition & 0 deletions packages/graphql/schemas/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ enum ErrorTypeEnum {
CDP_VERSION_TOO_OLD
CHROME_WEB_SECURITY_NOT_SUPPORTED
COMPONENT_FOLDER_REMOVED
COMPONENT_TESTING_MISMATCHED_DEPENDENCIES
CONFIG_FILES_LANGUAGE_CONFLICT
CONFIG_FILE_DEV_SERVER_INVALID_RETURN
CONFIG_FILE_DEV_SERVER_IS_NOT_VALID
Expand Down
52 changes: 52 additions & 0 deletions packages/launchpad/cypress/e2e/config-warning.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,55 @@ describe('experimentalStudio', () => {
cy.get('[data-cy="warning-alert"]').contains('Warning: Experimental Studio Removed')
})
})

describe('component testing dependency warnings', () => {
it('warns against outdated react and vite version', () => {
cy.scaffoldProject('component-testing-outdated-dependencies')
cy.addProject('component-testing-outdated-dependencies')
cy.openGlobalMode()
cy.visitLaunchpad()
cy.contains('component-testing-outdated-dependencies').click()
cy.get('[data-cy="warning-alert"]').should('not.exist')
cy.get('a').contains('Projects').click()
cy.get('[data-cy-testingtype="component"]').click()
cy.get('[data-cy="warning-alert"]').should('exist')
.should('contain.text', 'Warning: Component Testing Mismatched Dependencies')
.should('contain.text', 'vite. Expected ^=2.0.0 || ^=3.0.0, found 2.0.0-beta.70')
.should('contain.text', 'react. Expected ^=16.0.0 || ^=17.0.0 || ^=18.0.0, found 15.6.2.')
.should('contain.text', 'react-dom. Expected ^=16.0.0 || ^=17.0.0 || ^=18.0.0 but dependency was not found.')

cy.get('.warning-markdown').find('li').should('have.length', 3)
})

it('warns against outdated @vue/cli dependency', () => {
cy.scaffoldProject('outdated-deps-vuecli3')
cy.addProject('outdated-deps-vuecli3')
cy.openGlobalMode()
cy.visitLaunchpad()
cy.contains('outdated-deps-vuecli3').click()
cy.get('[data-cy="warning-alert"]').should('not.exist')
cy.get('a').contains('Projects').click()
cy.get('[data-cy-testingtype="component"]').click()
cy.get('[data-cy="warning-alert"]').should('exist')
.should('contain.text', 'Warning: Component Testing Mismatched Dependencies')
.should('contain.text', '@vue/cli-service. Expected ^=4.0.0 || ^=5.0.0, found 3.12.1.')
.should('contain.text', 'vue. Expected ^3.0.0, found 2.7.8.')

cy.get('.warning-markdown').find('li').should('have.length', 2)
})

it('does not show warning for project with supported dependencies', () => {
cy.scaffoldProject('vueclivue3-configured')
cy.addProject('vueclivue3-configured')
cy.openGlobalMode()
cy.visitLaunchpad()
cy.contains('vueclivue3-configured').click()
cy.get('[data-cy="warning-alert"]').should('not.exist')
cy.get('a').contains('Projects').click()
cy.get('[data-cy-testingtype="component"]').click()

// Wait until launch browser screen and assert warning does not exist
cy.contains('Choose a Browser')
cy.get('[data-cy="warning-alert"]').should('not.exist')
})
})
14 changes: 7 additions & 7 deletions packages/scaffold-config/src/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const WIZARD_DEPENDENCY_WEBPACK = {
package: 'webpack',
installer: 'webpack',
description: 'Webpack is a module bundler',
minVersion: '>=4.0.0',
minVersion: '>=4.0.0 || >=5.0.0',
} as const

export const WIZARD_DEPENDENCY_VUE_2 = {
Expand All @@ -31,7 +31,7 @@ export const WIZARD_DEPENDENCY_REACT = {
package: 'react',
installer: 'react',
description: 'A JavaScript library for building user interfaces',
minVersion: '>=16.x',
minVersion: '^=16.0.0 || ^=17.0.0 || ^=18.0.0',
} as const

export const WIZARD_DEPENDENCY_REACT_DOM = {
Expand All @@ -40,7 +40,7 @@ export const WIZARD_DEPENDENCY_REACT_DOM = {
package: 'react-dom',
installer: 'react-dom',
description: 'This package serves as the entry point to the DOM and server renderers for React',
minVersion: '>=16.x',
minVersion: '^=16.0.0 || ^=17.0.0 || ^=18.0.0',
} as const

export const WIZARD_DEPENDENCY_TYPESCRIPT = {
Expand All @@ -58,7 +58,7 @@ export const WIZARD_DEPENDENCY_REACT_SCRIPTS = {
package: 'react-scripts',
installer: 'react-scripts',
description: 'Create React apps with no build configuration',
minVersion: '>=4.0.0',
minVersion: '^=4.0.0 || ^=5.0.0',
} as const

export const WIZARD_DEPENDENCY_VUE_CLI_SERVICE = {
Expand All @@ -67,7 +67,7 @@ export const WIZARD_DEPENDENCY_VUE_CLI_SERVICE = {
package: '@vue/cli-service',
installer: '@vue/cli-service',
description: 'Standard Tooling for Vue.js Development',
minVersion: '>=4.0.0',
minVersion: '^=4.0.0 || ^=5.0.0',
} as const

export const WIZARD_DEPENDENCY_VITE = {
Expand All @@ -76,7 +76,7 @@ export const WIZARD_DEPENDENCY_VITE = {
package: 'vite',
installer: 'vite',
description: 'Vite is dev server that serves your source files over native ES modules',
minVersion: '>=2.0.0',
minVersion: '^=2.0.0 || ^=3.0.0',
} as const

export const WIZARD_DEPENDENCY_NUXT = {
Expand All @@ -94,7 +94,7 @@ export const WIZARD_DEPENDENCY_NEXT = {
package: 'next',
installer: 'next',
description: 'The React Framework for Production',
minVersion: '>=10.0.0',
minVersion: '^=10.0.0 || ^=11.0.0 || ^=12.0.0',
} as const

export const WIZARD_DEPENDENCY_ANGULAR_CLI = {
Expand Down
9 changes: 6 additions & 3 deletions packages/scaffold-config/src/frameworks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,20 @@ export function inPkgJson (dependency: WizardDependency, projectPath: string): D
// TODO: convert to async FS method
// eslint-disable-next-line no-restricted-syntax
const pkg = fs.readJsonSync(loc) as PkgJson
const pkgVersion = semver.coerce(pkg.version)

if (!pkgVersion) {
if (!pkg.version) {
throw Error(`${pkg.version} for ${dependency.package} is not a valid semantic version.`)
}

const satisfied = Boolean(pkg.version && semver.satisfies(pkg.version, dependency.minVersion, {
includePrerelease: true,
}))

return {
dependency,
detectedVersion: pkg.version,
loc,
satisfied: Boolean(pkg.version && semver.satisfies(pkgVersion, dependency.minVersion)),
satisfied,
}
} catch (e) {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
component: {
supportFile: false,
devServer: {
bundler: 'vite',
framework: 'react',
},
indexHtmlFile: 'cypress/component/support/component-index.html',
},
}
Loading

5 comments on commit cbb5e35

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on cbb5e35 Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.4.0/linux-x64/develop-cbb5e35762b6aba2529c365abd073c636bd27a7a/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on cbb5e35 Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.4.0/linux-arm64/develop-cbb5e35762b6aba2529c365abd073c636bd27a7a/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on cbb5e35 Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.4.0/darwin-arm64/develop-cbb5e35762b6aba2529c365abd073c636bd27a7a/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on cbb5e35 Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.4.0/darwin-x64/develop-cbb5e35762b6aba2529c365abd073c636bd27a7a/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on cbb5e35 Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.4.0/win32-x64/develop-cbb5e35762b6aba2529c365abd073c636bd27a7a/cypress.tgz

Please sign in to comment.