Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Public API for CT Framework Definitions #25780

Merged
merged 32 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3ad71ea
chore: rework component onboarding in launchpad (#25713)
lmiller1990 Feb 13, 2023
17cccbf
chore: typing error
lmiller1990 Feb 13, 2023
9c94642
feat: scan for 3rd party ct plugins (#25749)
ZachJW34 Feb 13, 2023
1477156
lint config
lmiller1990 Feb 13, 2023
8886944
spacing
lmiller1990 Feb 13, 2023
16ca37c
try fix race cond
lmiller1990 Feb 13, 2023
664e159
fix import error
lmiller1990 Feb 13, 2023
1a13d81
build binary
lmiller1990 Feb 13, 2023
86f4c02
try update snapshot
lmiller1990 Feb 13, 2023
b2f6343
try using require
lmiller1990 Feb 13, 2023
f61995b
Merge branch 'develop' into feature/ct-public-api
ZachJW34 Feb 13, 2023
bfa2d60
support namespaced definitions (#25804)
lmiller1990 Feb 15, 2023
217c104
Merge branch 'develop' into feature/ct-public-api
lmiller1990 Feb 15, 2023
ab820f0
Merge branch 'develop' into feature/ct-public-api
lmiller1990 Feb 16, 2023
1552adf
Merge branch 'develop' into feature/ct-public-api
lmiller1990 Feb 17, 2023
9b796c8
remove category
lmiller1990 Feb 17, 2023
a6a71e1
Merge branch 'feature/ct-public-api' of https://github.com/cypress-io…
lmiller1990 Feb 17, 2023
eb652ac
Merge branch 'develop' into feature/ct-public-api
lmiller1990 Feb 20, 2023
598be2d
add icon prop
lmiller1990 Feb 20, 2023
aaf91b8
Merge branch 'feature/ct-public-api' of https://github.com/cypress-io…
lmiller1990 Feb 20, 2023
fb47378
support esm -> cjs compiled typescript
lmiller1990 Feb 20, 2023
8b01351
fix test
lmiller1990 Feb 20, 2023
af70eff
misc: add CTA footer to launchpad framework dropdown (#25831)
astone123 Feb 20, 2023
14d131d
remove test project dependencies
astone123 Feb 21, 2023
25733db
rebase
lmiller1990 Feb 21, 2023
52e2f7f
windows
lmiller1990 Feb 21, 2023
e1ee825
windows again
lmiller1990 Feb 21, 2023
7fc8122
Merge branch 'develop' into feature/ct-public-api
lmiller1990 Feb 21, 2023
9d3a00b
add changelog entry
lmiller1990 Feb 21, 2023
84bc215
changelog
lmiller1990 Feb 21, 2023
f0d9542
revert workflow
lmiller1990 Feb 21, 2023
75733be
remove worklfow
lmiller1990 Feb 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .circleci/workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ windowsWorkflowFilters: &windows-workflow-filters
or:
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', 'fix-duplicate-and-expired-cookies', << pipeline.git.branch >> ]
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand Down Expand Up @@ -134,7 +134,7 @@ commands:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "astone123/ventura-webpack-permission-testing" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
Expand Down
3 changes: 2 additions & 1 deletion cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
## 12.6.1
## 12.7.0

_Released 03/1/2023 (PENDING)_

**Features:**

- 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).
- 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).

**Bugfixes:**

Expand Down
2 changes: 2 additions & 0 deletions cli/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export default cypress

export const defineConfig = cypress.defineConfig

export const defineComponentFramework = cypress.defineComponentFramework

export const run = cypress.run

export const open = cypress.open
Expand Down
18 changes: 18 additions & 0 deletions cli/lib/cypress.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ const cypressModuleApi = {
defineConfig (config) {
return config
},

/**
* Provides automatic code completion for Component Frameworks Definitions.
* While it's not strictly necessary for Cypress to parse your configuration, we
* recommend wrapping your Component Framework Definition object with `defineComponentFramework()`
* @example
* module.exports = defineComponentFramework({
* type: 'cypress-ct-solid-js'
* // ...
* })
*
* @see ../types/cypress-npm-api.d.ts
* @param {Cypress.ThirdPartyComponentFrameworkDefinition} config
* @returns {Cypress.ThirdPartyComponentFrameworkDefinition} the configuration passed in parameter
*/
defineComponentFramework (config) {
return config
},
}

module.exports = cypressModuleApi
15 changes: 15 additions & 0 deletions cli/types/cypress-npm-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,21 @@ declare module 'cypress' {
* @returns {Cypress.ConfigOptions} the configuration passed in parameter
*/
defineConfig<ComponentDevServerOpts = any>(config: Cypress.ConfigOptions<ComponentDevServerOpts>): Cypress.ConfigOptions

/**
* Provides automatic code completion for Component Frameworks Definitions.
* While it's not strictly necessary for Cypress to parse your configuration, we
* recommend wrapping your Component Framework Definition object with `defineComponentFramework()`
* @example
* module.exports = defineComponentFramework({
* type: 'cypress-ct-solid-js'
* })
*
* @see ../types/cypress-npm-api.d.ts
* @param {Cypress.ThirdPartyComponentFrameworkDefinition} config
* @returns {Cypress.ThirdPartyComponentFrameworkDefinition} the configuration passed in parameter
*/
defineComponentFramework(config: Cypress.ThirdPartyComponentFrameworkDefinition): Cypress.ThirdPartyComponentFrameworkDefinition
}

// export Cypress NPM module interface
Expand Down
173 changes: 173 additions & 0 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3283,6 +3283,179 @@ declare namespace Cypress {

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

interface DependencyToInstall {
dependency: CypressComponentDependency
satisfied: boolean
loc: string | null
detectedVersion: string | null
}

interface CypressComponentDependency {
/**
* Unique idenitifer.
* @example 'reactscripts'
*/
type: string

/**
* Name to display in the user interface.
* @example "React Scripts"
*/
name: string

/**
* Package name on npm.
* @example react-scripts
*/
package: string

/**
* Code to run when installing. Version is optional.
*
* Should be <package_name>@<version>.
*
* @example `react`
* @example `react@18`
* @example `react-scripts`
*/
installer: string

/**
* Description shown in UI. It is recommended to use the same one the package uses on npm.
* @example 'Create React apps with no build configuration'
*/
description: string

/**
* Minimum version supported. Should conform to Semantic Versioning as used in `package.json`.
* @see https://docs.npmjs.com/cli/v9/configuring-npm/package-json#dependencies
* @example '^=4.0.0 || ^=5.0.0'
* @example '^2.0.0'
*/
minVersion: string
}

interface ResolvedComponentFrameworkDefinition {
/**
* A semantic, unique identifier.
* Must begin with `cypress-ct-` or `@org/cypress-ct-` for third party implementations.
* @example 'reactscripts'
* @example 'nextjs'
* @example 'cypress-ct-solid-js'
*/
type: string

/**
* Used as the flag for `getPreset` for meta framworks, such as finding the webpack config for CRA, Angular, etc.
* It is also the name of the string added to `cypress.config`
*
* @example
* export default {
* component: {
* devServer: {
* framework: 'create-react-app' // can be 'next', 'create-react-app', etc etc.
* }
* }
* }
*/
configFramework: string

/**
* Library (React, Vue) or template (aka "meta framework") (CRA, Next.js, Angular)
*/
category: 'library' | 'template'

/**
* Name displayed in Launchpad when doing initial setup.
* @example 'Solid.js'
* @example 'Create React App'
*/
name: string

/**
* Supported bundlers.
*/
supportedBundlers: Array<'webpack' | 'vite'>

/**
* Used to attempt to automatically select the correct framework/bundler from the dropdown.
*
* @example
* const SOLID_DETECTOR: Dependency = {
* type: 'solid',
* name: 'Solid.js',
* package: 'solid-js',
* installer: 'solid-js',
* description: 'Solid is a declarative JavaScript library for creating user interfaces',
* minVersion: '^1.0.0',
* }
*/
detectors: CypressComponentDependency[]

/**
* Array of required dependencies. This could be the bundler and JavaScript library.
*/
dependencies: (bundler: 'webpack' | 'vite', projectPath: string) => Promise<DependencyToInstall[]>

/**
* This is used interally by Cypress for the "Create From Component" feature.
*/
codeGenFramework?: 'react' | 'vue' | 'svelte' | 'angular'

/**
* This is used interally by Cypress for the "Create From Component" feature.
* @example '*.{js,jsx,tsx}'
*/
glob?: string

/**
* This is the path to get mount, eg `import { mount } from <mount_module>,
* @example: `cypress-ct-solidjs/src/mount`
*/
mountModule: (projectPath: string) => Promise<string>

/**
* Support status. Internally alpha | beta | full.
* Community integrations are "community".
*/
supportStatus: 'alpha' | 'beta' | 'full' | 'community'

/**
* Function returning string for used for the component-index.html file.
* Cypress provides a default if one isn't specified for third party integrations.
*/
componentIndexHtml?: () => string

/**
* Used for the Create From Comopnent feature.
* This is currently not supported for third party frameworks.
*/
specPattern?: '**/*.cy.ts'
}

type ComponentFrameworkDefinition = Omit<ResolvedComponentFrameworkDefinition, 'dependencies'> & {
dependencies: (bundler: 'webpack' | 'vite') => CypressComponentDependency[]
}

/**
* Certain properties are not supported for third party frameworks right now,
* such as ones related to the "Create From" feature. This is a subset of
* properties that are exposed for public usage.
*/

type ThirdPartyComponentFrameworkDefinition = Pick<ComponentFrameworkDefinition, 'type' | 'name' | 'supportedBundlers' | 'detectors' | 'dependencies'> & {
/**
* @example `cypress-ct-${string} for third parties. Any string is valid internally.
*/
type: string

/**
* Raw SVG icon that will be displayed in the Project Setup Wizard. Used for third parties that
* want to render a custom icon.
*/
icon?: string
}

interface AngularDevServerProjectConfig {
root: string
sourceRoot: string
Expand Down
28 changes: 27 additions & 1 deletion cli/types/tests/cypress-npm-api-test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// type tests for Cypress NPM module
// https://on.cypress.io/module-api
import cypress, { defineConfig } from 'cypress'
import cypress, { defineComponentFramework, defineConfig } from 'cypress'

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

const solid = {
type: 'solid-js',
name: 'Solid.js',
package: 'solid-js',
installer: 'solid-js',
description: 'Solid is a declarative JavaScript library for creating user interfaces',
minVersion: '^1.0.0'
}

const thirdPartyFrameworkDefinition = defineComponentFramework({
type: 'cypress-ct-third-party',
name: 'Third Party',
dependencies: (bundler) => [solid],
detectors: [solid],
supportedBundlers: ['vite', 'webpack'],
icon: '<svg>...</svg>'
})

const thirdPartyFrameworkDefinitionInvalidStrings = defineComponentFramework({
type: 'cypress-ct-third-party',
name: 'Third Party',
dependencies: (bundler) => [],
detectors: [{}], // $ExpectError
supportedBundlers: ['metro', 'webpack'] // $ExpectError
})

// component options
const componentConfigNextWebpack: Cypress.ConfigOptions = {
component: {
Expand Down
20 changes: 19 additions & 1 deletion npm/webpack-dev-server/src/devServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,25 @@ export type PresetHandlerResult = { frameworkConfig: Configuration, sourceWebpac

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

const thirdPartyDefinitionPrefixes = {
// matches @org/cypress-ct-*
namespacedPrefixRe: /^@.+?\/cypress-ct-.+/,
globalPrefix: 'cypress-ct-',
}

export function isThirdPartyDefinition (framework: string) {
return framework.startsWith(thirdPartyDefinitionPrefixes.globalPrefix) ||
thirdPartyDefinitionPrefixes.namespacedPrefixRe.test(framework)
}

async function getPreset (devServerConfig: WebpackDevServerConfig): Promise<Optional<PresetHandlerResult, 'frameworkConfig'>> {
const defaultWebpackModules = () => ({ sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) })

// Third party library (eg solid-js, lit, etc)
if (devServerConfig.framework && isThirdPartyDefinition(devServerConfig.framework)) {
return defaultWebpackModules()
}

switch (devServerConfig.framework) {
case 'create-react-app':
return createReactAppHandler(devServerConfig)
Expand All @@ -134,7 +152,7 @@ async function getPreset (devServerConfig: WebpackDevServerConfig): Promise<Opti
case 'vue':
case 'svelte':
case undefined:
return { sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) }
return defaultWebpackModules()

default:
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`)
Expand Down
5 changes: 2 additions & 3 deletions packages/data-context/src/actions/CodegenActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type { DataContext } from '..'
import { SpecOptions, codeGenerator } from '../codegen'
import templates from '../codegen/templates'
import type { CodeGenType } from '../gen/graphcache-config.gen'
import { WizardFrontendFramework, WIZARD_FRAMEWORKS } from '@packages/scaffold-config'
import { parse as parseReactComponent, resolver as reactDocgenResolvers } from 'react-docgen'
import { visit } from 'ast-types'

Expand Down Expand Up @@ -152,7 +151,7 @@ export class CodegenActions {
})
}

getWizardFrameworkFromConfig (): WizardFrontendFramework | undefined {
getWizardFrameworkFromConfig (): Cypress.ResolvedComponentFrameworkDefinition | undefined {
const config = this.ctx.lifecycleManager.loadedConfigFile

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

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

Expand Down
Loading