diff --git a/.github/README.md b/.github/README.md index 3a3dd22..29a1935 100644 --- a/.github/README.md +++ b/.github/README.md @@ -16,6 +16,33 @@ Integrate Cypress with alternative cloud services like Currents or Sorry Cypress

+## Table of Contents + +- [Requirements](#requirements) +- [Setup](#setup) +- [Usage](#usage) +- [Example](#example) +- [Configuration](#configuration) + - [Configuration File Discovery](#configuration-file-discovery) + - [Configuration Overrides](#configuration-overrides) +- [Batched Orchestration](#batched-orchestration) +- [API](#api) + - [`run`](#run) +- [Guides](#guides) + - [Usage with `@cypress/grep`](#usage-with-cypressgrep) + - [Setup with existing plugins](#setup-with-existing-plugins) + - [Preserving `config.env` values](#preserving-configenv-values) + - [Chaining `config`](#chaining-config) + - [Event callbacks for multiple plugins](#event-callbacks-for-multiple-plugins) +- [Spec files discovery](#spec-files-discovery) +- [Usage with ESM project](#usage-with-esm-project) +- [Troubleshooting](#troubleshooting) +- [Testing](#testing) +- [Releasing](#releasing) + - [Beta channel](#beta-channel) + - [Latest channel](#latest-channel) + - [Localhost](#localhost) + ## Requirements - Cypress version 10+ @@ -44,14 +71,15 @@ module.exports = { Add `cypress-cloud/plugin` to `cypress.config.{js|ts|mjs}` -```js +```ts // cypress.config.js -const { defineConfig } = require("cypress"); -const { cloudPlugin } = require("cypress-cloud/plugin"); -module.exports = defineConfig({ +import { defineConfig } from "cypress"; +import { cloudPlugin } from "cypress-cloud/plugin"; +export default defineConfig({ e2e: { - setupNodeEvents(on, config) { - return cloudPlugin(on, config); + async setupNodeEvents(on, config) { + const result = await cloudPlugin(on, config); + return result; }, }, }); @@ -60,7 +88,7 @@ module.exports = defineConfig({ Add `cypress-cloud/support` to Cypress Support file (matching your test type - e2e or component, or both) ```ts -import `cypress-cloud/support` +import "cypress-cloud/support"; ``` ## Usage @@ -167,48 +195,108 @@ const results = await run({ ### Usage with `@cypress/grep` -The package is compatible with [`@cypress/grep`](https://www.npmjs.com/package/@cypress/grep). Make sure to run `require("@cypress/grep/src/plugin")(config);` before `await currents(on, config);`, for example: +The package is compatible with [`@cypress/grep`](https://www.npmjs.com/package/@cypress/grep). -```js -setupNodeEvents(on, config) { - require("cypress-terminal-report/src/installLogsPrinter")(on); - require("@cypress/grep/src/plugin")(config); - return currents(on, config); -} +`@cypress/grep` modifies cypress configuration and alters `specPattern` property. Install `@cypress/grep` **before** `cypress-cloud/plugin` to apply the modified configuration. For example: + +```ts +import { defineConfig } from "cypress"; +import grepPlugin from "@cypress/grep/src/plugin"; +import { cloudPlugin } from "cypress-cloud/plugin"; + +export default defineConfig({ + e2e: { + // ... + async setupNodeEvents(on, config) { + grepPlugin(config); + const result = await cloudPlugin(on, config); + return result; + }, + }, +}); ``` Please refer to the [issue](https://github.com/currents-dev/cypress-cloud/issues/50#issuecomment-1645095284) for details. ### Setup with existing plugins -`cypress-cloud/plugin` needs access to certain environment variables that are injected into the `config` parameter of `setupNodeEvents(on, config)`. +#### Preserving `config.env` values -Please make sure to preserve the original `config.env` parameters in case you are using additional plugins, e.g.: +The `config` parameter of `setupNodeEvents(on, config)` has pre-defined `config.env` values. Please make sure to preserve the original `config.env` value when altering the property. For example: -```js -const { defineConfig } = require("cypress"); -const { cloudPlugin } = require("cypress-cloud/plugin"); +```ts +import { defineConfig } from "cypress"; +import { cloudPlugin } from "cypress-cloud/plugin"; -module.exports = defineConfig({ +export default defineConfig({ e2e: { // ... - setupNodeEvents(on, config) { - // alternative: activate the plugin first - // cloudPlugin(on, config) + async setupNodeEvents(on, config) { const enhancedConfig = { env: { - // preserve the original env - ...config.env, + ...config.env, // 👈🏻 preserve the original env customVariable: "value", }, }; - return cloudPlugin(on, enhancedConfig); + const result = await cloudPlugin(on, enhancedConfig); + return result; + }, + }, +}); +``` + +#### Chaining `config` + +Certain plugins (e.g. `@cypress/grep`) modify or alter the `config` parameter and change the default Cypress behaviour. Make sure that `cypress-cloud` is initialized with the most recently updated `config`, e.g.: + +```ts +import { defineConfig } from "cypress"; +import { cloudPlugin } from "cypress-cloud/plugin"; + +export default defineConfig({ + e2e: { + // ... + async setupNodeEvents(on, config) { + const configA = pluginA(on, config); // configA has the modified config from pluginA + const configB = pluginB(on, configA); // configA has the modified config from pluginA + pluginB + // ... + const configX = pluginX(on, configY); // configX has the modified config from all preceding plugins + const result = await cloudPlugin(on, configX); // cloudPlugin has the accumulated config from all plugins + return result; }, }, }); ``` -As an alternative, you can activate the `cloudPlugin` first, and then implement the custom setup. Please contact our support if you have a complex plugin configuration to get assistance with the setup. +#### Event callbacks for multiple plugins + +`cypress-cloud/plugin` uses certain Cypress Plugin events. Unfortunately if there are mutliple listeners for an event, only the last listener is called (see the [GitHub issue](https://github.com/cypress-io/cypress/issues/22428)). Setups with multiple plugins can create conflicts - one plugin can replace listeners of others. + +The existing workaround is to patch the `on` function by using either of: + +- https://github.com/bahmutov/cypress-on-fix +- https://github.com/elaichenkov/cypress-plugin-init + +For example: + +```ts +import { defineConfig } from "cypress"; +import { cloudPlugin } from "cypress-cloud/plugin"; +import patchCypressOn from "cypress-on-fix"; + +export default defineConfig({ + e2e: { + // ... + async setupNodeEvents(cypressOn, config) { + const on = patchCypressOn(cypressOn); + // the rest of the plugins use the patched "on" function + const configAlt = somePlugin(on, config); + const result = await cloudPlugin(on, configXconfigAlt); + return result; + }, + }, +}); +``` ### Spec files discovery diff --git a/examples/webapp/cypress.config.ts b/examples/webapp/cypress.config.ts index f50a480..39e2aad 100644 --- a/examples/webapp/cypress.config.ts +++ b/examples/webapp/cypress.config.ts @@ -1,5 +1,7 @@ +import grepPlugin from "@cypress/grep/src/plugin"; import { defineConfig } from "cypress"; -import currents from "cypress-cloud/plugin"; +import { cloudPlugin } from "cypress-cloud/plugin"; +import patchCypressOn from "cypress-on-fix"; module.exports = defineConfig({ e2e: { @@ -10,17 +12,19 @@ module.exports = defineConfig({ videoUploadOnPasses: false, supportFile: "cypress/support/e2e.ts", specPattern: "cypress/*/**/*.spec.js", - setupNodeEvents(on, config) { - require("@cypress/grep/src/plugin")(config); - require("cypress-terminal-report/src/installLogsPrinter")(on); - return currents(on, config); + async setupNodeEvents(cyOn, config) { + const on = patchCypressOn(cyOn); + grepPlugin(config); + const result = await cloudPlugin(on, config); + return result; }, }, component: { specPattern: ["pages/__tests__/*.spec.tsx"], - setupNodeEvents(on, config) { - return currents(on, config); + async setupNodeEvents(on, config) { + const result = await cloudPlugin(on, config); + return result; }, devServer: { framework: "next", diff --git a/examples/webapp/cypress/support/e2e.ts b/examples/webapp/cypress/support/e2e.ts index 32bd259..36c4990 100644 --- a/examples/webapp/cypress/support/e2e.ts +++ b/examples/webapp/cypress/support/e2e.ts @@ -1,5 +1,4 @@ import registerCypressGrep from "@cypress/grep/src/support"; -require("cypress-terminal-report/src/installLogsCollector")(); require("cypress-cloud/support"); require("./commands"); diff --git a/examples/webapp/package.json b/examples/webapp/package.json index 7fcee08..ddc938f 100644 --- a/examples/webapp/package.json +++ b/examples/webapp/package.json @@ -12,6 +12,7 @@ "dependencies": { "cypress": "^12.6.0", "cypress-cloud": "*", + "cypress-on-fix": "^1.0.2", "cypress-terminal-report": "^5.3.3", "next": "^13.2.1", "react": "^18.2.0", diff --git a/package-lock.json b/package-lock.json index 6f85334..74c0cb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -255,6 +255,7 @@ "dependencies": { "cypress": "^12.6.0", "cypress-cloud": "*", + "cypress-on-fix": "^1.0.2", "cypress-terminal-report": "^5.3.3", "next": "^13.2.1", "react": "^18.2.0", @@ -7414,6 +7415,11 @@ "resolved": "packages/cypress-cloud", "link": true }, + "node_modules/cypress-on-fix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cypress-on-fix/-/cypress-on-fix-1.0.2.tgz", + "integrity": "sha512-oN/PW7FsC3Y7xC9Z76KzAVXWH5VNPrt+FWjMlSYDPQtEOYedVNBLu7dz9CEU6Ntt5kD0MHuXAyO8Ov2YNBf9lg==" + }, "node_modules/cypress-terminal-report": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/cypress-terminal-report/-/cypress-terminal-report-5.3.3.tgz", @@ -23217,6 +23223,11 @@ } } }, + "cypress-on-fix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cypress-on-fix/-/cypress-on-fix-1.0.2.tgz", + "integrity": "sha512-oN/PW7FsC3Y7xC9Z76KzAVXWH5VNPrt+FWjMlSYDPQtEOYedVNBLu7dz9CEU6Ntt5kD0MHuXAyO8Ov2YNBf9lg==" + }, "cypress-terminal-report": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/cypress-terminal-report/-/cypress-terminal-report-5.3.3.tgz", @@ -28497,6 +28508,7 @@ "@types/react-dom": "^18.0.11", "cypress": "^12.6.0", "cypress-cloud": "*", + "cypress-on-fix": "^1.0.2", "cypress-terminal-report": "^5.3.3", "eslint": "7.32.0", "eslint-config-custom": "latest",