Skip to content

Commit

Permalink
feat: Public API for CT Framework Definitions (#25780)
Browse files Browse the repository at this point in the history
* 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 <ZachJW34@gmail.com>

* 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 <lachlan.miller.1990@outlook.com>

* 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 <ZachJW34@gmail.com>
Co-authored-by: Adam Stone-Lord <adams@cypress.io>
  • Loading branch information
3 people authored Feb 21, 2023
1 parent 46b35d3 commit 1d3aab9
Show file tree
Hide file tree
Showing 74 changed files with 2,344 additions and 269 deletions.
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

5 comments on commit 1d3aab9

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 1d3aab9 Feb 21, 2023

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 build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.7.0/linux-arm64/develop-1d3aab9d70acbce6d3571ab5b9df771f1c455964/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 1d3aab9 Feb 21, 2023

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 build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.7.0/linux-x64/develop-1d3aab9d70acbce6d3571ab5b9df771f1c455964/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 1d3aab9 Feb 21, 2023

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 build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.7.0/darwin-x64/develop-1d3aab9d70acbce6d3571ab5b9df771f1c455964/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 1d3aab9 Feb 21, 2023

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 build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.7.0/darwin-arm64/develop-1d3aab9d70acbce6d3571ab5b9df771f1c455964/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 1d3aab9 Feb 21, 2023

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 build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.7.0/win32-x64/develop-1d3aab9d70acbce6d3571ab5b9df771f1c455964/cypress.tgz

Please sign in to comment.