Skip to content

Commit 1d3aab9

Browse files
lmiller1990ZachJW34astone123
authored
feat: Public API for CT Framework Definitions (#25780)
* chore: rework component onboarding in launchpad (#25713) * chore: refactoring and types * rework source of frameworks * revert rename * fix tests * fix more tests * types * update code * use same public API internally * rename interfaces * rename * work on dev server api * fix types * fix test * attempt to support getDevServerConfig * tests * add function to define framework [skip ci] * rework a lot of types * fix test * update tests and types * refactor * revert changes * lint * fix test * revert * remove * add "community" label [skip ci] * refactor * types * lint * fix bug * update function name * address feedback * improve types with Pick * refactor using type guard * correct label --------- Co-authored-by: Zachary Williams <[email protected]> * chore: typing error * feat: scan for 3rd party ct plugins (#25749) * chore: refactoring and types * rework source of frameworks * revert rename * fix tests * fix more tests * types * update code * use same public API internally * rename interfaces * rename * work on dev server api * fix types * fix test * attempt to support getDevServerConfig * tests * add function to define framework [skip ci] * rework a lot of types * fix test * update tests and types * refactor * revert changes * lint * fix test * revert * remove * add "community" label [skip ci] * refactor * types * lint * fix bug * update function name * address feedback * feat: scan for 3rd party ct plugins * add e2e test * unit tests [run ci] * tweak resolution * rebase, address comments * fix windows paths * remove .gitignore * fix test --------- Co-authored-by: Lachlan Miller <[email protected]> * lint config * spacing * try fix race cond * fix import error * build binary * try update snapshot * try using require * support namespaced definitions (#25804) * remove category * add icon prop * support esm -> cjs compiled typescript * fix test * misc: add CTA footer to launchpad framework dropdown (#25831) * remove test project dependencies * rebase * windows * windows again * add changelog entry * changelog * revert workflow * remove worklfow --------- Co-authored-by: Zachary Williams <[email protected]> Co-authored-by: Adam Stone-Lord <[email protected]>
1 parent 46b35d3 commit 1d3aab9

File tree

74 files changed

+2344
-269
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+2344
-269
lines changed

.circleci/workflows.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ windowsWorkflowFilters: &windows-workflow-filters
6868
or:
6969
- equal: [ develop, << pipeline.git.branch >> ]
7070
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
71-
- equal: [ 'update-v8-snapshot-cache-on-develop', 'fix-duplicate-and-expired-cookies', << pipeline.git.branch >> ]
71+
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
7272
- matches:
7373
pattern: /^release\/\d+\.\d+\.\d+$/
7474
value: << pipeline.git.branch >>
@@ -134,7 +134,7 @@ commands:
134134
- run:
135135
name: Check current branch to persist artifacts
136136
command: |
137-
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "astone123/ventura-webpack-permission-testing" ]]; then
137+
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* ]]; then
138138
echo "Not uploading artifacts or posting install comment for this branch."
139139
circleci-agent step halt
140140
fi

cli/CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
2-
## 12.6.1
2+
## 12.7.0
33

44
_Released 03/1/2023 (PENDING)_
55

66
**Features:**
77

88
- It is now possible to set `hostOnly` cookies with [`cy.setCookie()`](https://docs.cypress.io/api/commands/setcookie) for a given domain. Addresses [#16856](https://github.com/cypress-io/cypress/issues/16856) and [#17527](https://github.com/cypress-io/cypress/issues/17527).
9+
- Added a Public API for third party component libraries to define a Framework Definition, embedding their library into the Cypress onboarding workflow. Learn more [here](https://docs.cypress.io/guides/component-testing/third-party-definitions). Implemented in [#25780](https://github.com/cypress-io/cypress/pull/25780) and closes [#25638](https://github.com/cypress-io/cypress/issues/25638).
910

1011
**Bugfixes:**
1112

cli/index.mjs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export default cypress
88

99
export const defineConfig = cypress.defineConfig
1010

11+
export const defineComponentFramework = cypress.defineComponentFramework
12+
1113
export const run = cypress.run
1214

1315
export const open = cypress.open

cli/lib/cypress.js

+18
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ const cypressModuleApi = {
8787
defineConfig (config) {
8888
return config
8989
},
90+
91+
/**
92+
* Provides automatic code completion for Component Frameworks Definitions.
93+
* While it's not strictly necessary for Cypress to parse your configuration, we
94+
* recommend wrapping your Component Framework Definition object with `defineComponentFramework()`
95+
* @example
96+
* module.exports = defineComponentFramework({
97+
* type: 'cypress-ct-solid-js'
98+
* // ...
99+
* })
100+
*
101+
* @see ../types/cypress-npm-api.d.ts
102+
* @param {Cypress.ThirdPartyComponentFrameworkDefinition} config
103+
* @returns {Cypress.ThirdPartyComponentFrameworkDefinition} the configuration passed in parameter
104+
*/
105+
defineComponentFramework (config) {
106+
return config
107+
},
90108
}
91109

92110
module.exports = cypressModuleApi

cli/types/cypress-npm-api.d.ts

+15
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,21 @@ declare module 'cypress' {
397397
* @returns {Cypress.ConfigOptions} the configuration passed in parameter
398398
*/
399399
defineConfig<ComponentDevServerOpts = any>(config: Cypress.ConfigOptions<ComponentDevServerOpts>): Cypress.ConfigOptions
400+
401+
/**
402+
* Provides automatic code completion for Component Frameworks Definitions.
403+
* While it's not strictly necessary for Cypress to parse your configuration, we
404+
* recommend wrapping your Component Framework Definition object with `defineComponentFramework()`
405+
* @example
406+
* module.exports = defineComponentFramework({
407+
* type: 'cypress-ct-solid-js'
408+
* })
409+
*
410+
* @see ../types/cypress-npm-api.d.ts
411+
* @param {Cypress.ThirdPartyComponentFrameworkDefinition} config
412+
* @returns {Cypress.ThirdPartyComponentFrameworkDefinition} the configuration passed in parameter
413+
*/
414+
defineComponentFramework(config: Cypress.ThirdPartyComponentFrameworkDefinition): Cypress.ThirdPartyComponentFrameworkDefinition
400415
}
401416

402417
// export Cypress NPM module interface

cli/types/cypress.d.ts

+173
Original file line numberDiff line numberDiff line change
@@ -3283,6 +3283,179 @@ declare namespace Cypress {
32833283

32843284
type PickConfigOpt<T> = T extends keyof DefineDevServerConfig ? DefineDevServerConfig[T] : any
32853285

3286+
interface DependencyToInstall {
3287+
dependency: CypressComponentDependency
3288+
satisfied: boolean
3289+
loc: string | null
3290+
detectedVersion: string | null
3291+
}
3292+
3293+
interface CypressComponentDependency {
3294+
/**
3295+
* Unique idenitifer.
3296+
* @example 'reactscripts'
3297+
*/
3298+
type: string
3299+
3300+
/**
3301+
* Name to display in the user interface.
3302+
* @example "React Scripts"
3303+
*/
3304+
name: string
3305+
3306+
/**
3307+
* Package name on npm.
3308+
* @example react-scripts
3309+
*/
3310+
package: string
3311+
3312+
/**
3313+
* Code to run when installing. Version is optional.
3314+
*
3315+
* Should be <package_name>@<version>.
3316+
*
3317+
* @example `react`
3318+
* @example `react@18`
3319+
* @example `react-scripts`
3320+
*/
3321+
installer: string
3322+
3323+
/**
3324+
* Description shown in UI. It is recommended to use the same one the package uses on npm.
3325+
* @example 'Create React apps with no build configuration'
3326+
*/
3327+
description: string
3328+
3329+
/**
3330+
* Minimum version supported. Should conform to Semantic Versioning as used in `package.json`.
3331+
* @see https://docs.npmjs.com/cli/v9/configuring-npm/package-json#dependencies
3332+
* @example '^=4.0.0 || ^=5.0.0'
3333+
* @example '^2.0.0'
3334+
*/
3335+
minVersion: string
3336+
}
3337+
3338+
interface ResolvedComponentFrameworkDefinition {
3339+
/**
3340+
* A semantic, unique identifier.
3341+
* Must begin with `cypress-ct-` or `@org/cypress-ct-` for third party implementations.
3342+
* @example 'reactscripts'
3343+
* @example 'nextjs'
3344+
* @example 'cypress-ct-solid-js'
3345+
*/
3346+
type: string
3347+
3348+
/**
3349+
* Used as the flag for `getPreset` for meta framworks, such as finding the webpack config for CRA, Angular, etc.
3350+
* It is also the name of the string added to `cypress.config`
3351+
*
3352+
* @example
3353+
* export default {
3354+
* component: {
3355+
* devServer: {
3356+
* framework: 'create-react-app' // can be 'next', 'create-react-app', etc etc.
3357+
* }
3358+
* }
3359+
* }
3360+
*/
3361+
configFramework: string
3362+
3363+
/**
3364+
* Library (React, Vue) or template (aka "meta framework") (CRA, Next.js, Angular)
3365+
*/
3366+
category: 'library' | 'template'
3367+
3368+
/**
3369+
* Name displayed in Launchpad when doing initial setup.
3370+
* @example 'Solid.js'
3371+
* @example 'Create React App'
3372+
*/
3373+
name: string
3374+
3375+
/**
3376+
* Supported bundlers.
3377+
*/
3378+
supportedBundlers: Array<'webpack' | 'vite'>
3379+
3380+
/**
3381+
* Used to attempt to automatically select the correct framework/bundler from the dropdown.
3382+
*
3383+
* @example
3384+
* const SOLID_DETECTOR: Dependency = {
3385+
* type: 'solid',
3386+
* name: 'Solid.js',
3387+
* package: 'solid-js',
3388+
* installer: 'solid-js',
3389+
* description: 'Solid is a declarative JavaScript library for creating user interfaces',
3390+
* minVersion: '^1.0.0',
3391+
* }
3392+
*/
3393+
detectors: CypressComponentDependency[]
3394+
3395+
/**
3396+
* Array of required dependencies. This could be the bundler and JavaScript library.
3397+
*/
3398+
dependencies: (bundler: 'webpack' | 'vite', projectPath: string) => Promise<DependencyToInstall[]>
3399+
3400+
/**
3401+
* This is used interally by Cypress for the "Create From Component" feature.
3402+
*/
3403+
codeGenFramework?: 'react' | 'vue' | 'svelte' | 'angular'
3404+
3405+
/**
3406+
* This is used interally by Cypress for the "Create From Component" feature.
3407+
* @example '*.{js,jsx,tsx}'
3408+
*/
3409+
glob?: string
3410+
3411+
/**
3412+
* This is the path to get mount, eg `import { mount } from <mount_module>,
3413+
* @example: `cypress-ct-solidjs/src/mount`
3414+
*/
3415+
mountModule: (projectPath: string) => Promise<string>
3416+
3417+
/**
3418+
* Support status. Internally alpha | beta | full.
3419+
* Community integrations are "community".
3420+
*/
3421+
supportStatus: 'alpha' | 'beta' | 'full' | 'community'
3422+
3423+
/**
3424+
* Function returning string for used for the component-index.html file.
3425+
* Cypress provides a default if one isn't specified for third party integrations.
3426+
*/
3427+
componentIndexHtml?: () => string
3428+
3429+
/**
3430+
* Used for the Create From Comopnent feature.
3431+
* This is currently not supported for third party frameworks.
3432+
*/
3433+
specPattern?: '**/*.cy.ts'
3434+
}
3435+
3436+
type ComponentFrameworkDefinition = Omit<ResolvedComponentFrameworkDefinition, 'dependencies'> & {
3437+
dependencies: (bundler: 'webpack' | 'vite') => CypressComponentDependency[]
3438+
}
3439+
3440+
/**
3441+
* Certain properties are not supported for third party frameworks right now,
3442+
* such as ones related to the "Create From" feature. This is a subset of
3443+
* properties that are exposed for public usage.
3444+
*/
3445+
3446+
type ThirdPartyComponentFrameworkDefinition = Pick<ComponentFrameworkDefinition, 'type' | 'name' | 'supportedBundlers' | 'detectors' | 'dependencies'> & {
3447+
/**
3448+
* @example `cypress-ct-${string} for third parties. Any string is valid internally.
3449+
*/
3450+
type: string
3451+
3452+
/**
3453+
* Raw SVG icon that will be displayed in the Project Setup Wizard. Used for third parties that
3454+
* want to render a custom icon.
3455+
*/
3456+
icon?: string
3457+
}
3458+
32863459
interface AngularDevServerProjectConfig {
32873460
root: string
32883461
sourceRoot: string

cli/types/tests/cypress-npm-api-test.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// type tests for Cypress NPM module
22
// https://on.cypress.io/module-api
3-
import cypress, { defineConfig } from 'cypress'
3+
import cypress, { defineComponentFramework, defineConfig } from 'cypress'
44

55
cypress.run // $ExpectType (options?: Partial<CypressRunOptions> | undefined) => Promise<CypressRunResult | CypressFailedRunResult>
66
cypress.open // $ExpectType (options?: Partial<CypressOpenOptions> | undefined) => Promise<void>
@@ -55,6 +55,32 @@ const config = defineConfig({
5555
modifyObstructiveCode: true
5656
})
5757

58+
const solid = {
59+
type: 'solid-js',
60+
name: 'Solid.js',
61+
package: 'solid-js',
62+
installer: 'solid-js',
63+
description: 'Solid is a declarative JavaScript library for creating user interfaces',
64+
minVersion: '^1.0.0'
65+
}
66+
67+
const thirdPartyFrameworkDefinition = defineComponentFramework({
68+
type: 'cypress-ct-third-party',
69+
name: 'Third Party',
70+
dependencies: (bundler) => [solid],
71+
detectors: [solid],
72+
supportedBundlers: ['vite', 'webpack'],
73+
icon: '<svg>...</svg>'
74+
})
75+
76+
const thirdPartyFrameworkDefinitionInvalidStrings = defineComponentFramework({
77+
type: 'cypress-ct-third-party',
78+
name: 'Third Party',
79+
dependencies: (bundler) => [],
80+
detectors: [{}], // $ExpectError
81+
supportedBundlers: ['metro', 'webpack'] // $ExpectError
82+
})
83+
5884
// component options
5985
const componentConfigNextWebpack: Cypress.ConfigOptions = {
6086
component: {

npm/webpack-dev-server/src/devServer.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,25 @@ export type PresetHandlerResult = { frameworkConfig: Configuration, sourceWebpac
114114

115115
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
116116

117+
const thirdPartyDefinitionPrefixes = {
118+
// matches @org/cypress-ct-*
119+
namespacedPrefixRe: /^@.+?\/cypress-ct-.+/,
120+
globalPrefix: 'cypress-ct-',
121+
}
122+
123+
export function isThirdPartyDefinition (framework: string) {
124+
return framework.startsWith(thirdPartyDefinitionPrefixes.globalPrefix) ||
125+
thirdPartyDefinitionPrefixes.namespacedPrefixRe.test(framework)
126+
}
127+
117128
async function getPreset (devServerConfig: WebpackDevServerConfig): Promise<Optional<PresetHandlerResult, 'frameworkConfig'>> {
129+
const defaultWebpackModules = () => ({ sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) })
130+
131+
// Third party library (eg solid-js, lit, etc)
132+
if (devServerConfig.framework && isThirdPartyDefinition(devServerConfig.framework)) {
133+
return defaultWebpackModules()
134+
}
135+
118136
switch (devServerConfig.framework) {
119137
case 'create-react-app':
120138
return createReactAppHandler(devServerConfig)
@@ -134,7 +152,7 @@ async function getPreset (devServerConfig: WebpackDevServerConfig): Promise<Opti
134152
case 'vue':
135153
case 'svelte':
136154
case undefined:
137-
return { sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) }
155+
return defaultWebpackModules()
138156

139157
default:
140158
throw new Error(`Unexpected framework ${(devServerConfig as any).framework}, please visit https://on.cypress.io/component-framework-configuration to see a list of supported frameworks`)

packages/data-context/src/actions/CodegenActions.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { DataContext } from '..'
55
import { SpecOptions, codeGenerator } from '../codegen'
66
import templates from '../codegen/templates'
77
import type { CodeGenType } from '../gen/graphcache-config.gen'
8-
import { WizardFrontendFramework, WIZARD_FRAMEWORKS } from '@packages/scaffold-config'
98
import { parse as parseReactComponent, resolver as reactDocgenResolvers } from 'react-docgen'
109
import { visit } from 'ast-types'
1110

@@ -152,7 +151,7 @@ export class CodegenActions {
152151
})
153152
}
154153

155-
getWizardFrameworkFromConfig (): WizardFrontendFramework | undefined {
154+
getWizardFrameworkFromConfig (): Cypress.ResolvedComponentFrameworkDefinition | undefined {
156155
const config = this.ctx.lifecycleManager.loadedConfigFile
157156

158157
// If devServer is a function, they are using a custom dev server.
@@ -161,7 +160,7 @@ export class CodegenActions {
161160
}
162161

163162
// @ts-ignore - because of the conditional above, we know that devServer isn't a function
164-
return WIZARD_FRAMEWORKS.find((framework) => framework.configFramework === config?.component?.devServer.framework)
163+
return this.ctx.coreData.wizard.frameworks.find((framework) => framework.configFramework === config?.component?.devServer.framework)
165164
}
166165
}
167166

0 commit comments

Comments
 (0)