diff --git a/.github/ISSUE_TEMPLATE/1.bug.yaml b/.github/ISSUE_TEMPLATE/1.bug.yaml
index ebf2ecbc..8a15d764 100644
--- a/.github/ISSUE_TEMPLATE/1.bug.yaml
+++ b/.github/ISSUE_TEMPLATE/1.bug.yaml
@@ -3,16 +3,31 @@ description: Create a report to help us improve cypress-cloud
labels: bug
body:
+ - type: markdown
+ attributes:
+ value: |
+ ## Before Opening a New Issue
+
+ Thank you for taking the time to open an issue!
+
+ `cypress-cloud` runs on various environments with different setups and configurations, we have to ask you to provide more information about your specific setup, otherwise we won't be able to help you.
+
+ Here are a few resources that can help you:
+
+ - [`cypress-cloud` Documentation](https://currents.dev/readme/integration-with-cypress)
+ - [Troubleshooting Guide](https://currents.dev/readme/integration-with-cypress/troubleshooting)
+ - [Common Configuration Pitfalls](https://github.com/currents-dev/cypress-cloud#setup-with-existing-plugins)
+
- type: checkboxes
attributes:
label: |
- Before opening, please confirm:
+ Please confirm
options:
- label: I have searched for [duplicate or closed issues](https://github.com/currents-dev/cypress-cloud/issues) and [discussions](https://github.com/currents-dev/cypress-cloud/discussions).
required: true
- - label: I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
+ - label: I will include a minimal, self-contained set of instructions for consistently reproducing the issue.
required: true
- - label: I acknowledge that I will attach a **full debug log**, otherwise the issue will be closed with no response.
+ - label: I will attach a **full debug log**, otherwise the issue will be closed with no response.
required: true
- type: markdown
@@ -23,9 +38,9 @@ body:
attributes:
label: Environment information
description: |
- Please run the following command inside your project and copy/paste the output below.
+ We need to know what package versions you're using. Please run the following command inside your project and copy/paste the output below.
- **šš» Run the command in the right environment šš», e.g. if the problem is in CI environment, run it in the CI environment.
+ **šš» Run the command in the right environment šš»**, e.g. if the problem is in CI environment, run it in the CI environment.
```
npx envinfo --system --binaries --browsers --npmPackages --duplicates --npmGlobalPackages
@@ -62,10 +77,10 @@ body:
- type: textarea
attributes:
- label: Command and Setup
+ label: Setup and Command
description: |
- - The exact command or code snippet
- - Your cloud provider or sorry-cypress setup details
+ - Your `cypress.config.js` file, including standalone plugins configuration.
+ - The exact command or code snippet used to run the tests.
validations:
required: true
@@ -74,17 +89,18 @@ body:
attributes:
label: Full log and debug output
description: |
- Run in debug mode to provide more info - error messages and stack traces.
+ Enable the [debug mode](https://currents.dev/readme/integration-with-cypress/troubleshooting#enabling-debug-mode-for-cypress-cloud-1.9.0+) to provide more info - error messages and stack traces.
- - **šš» Include the full log šš» - starting from running the command till receiving an error.**
+ - Follow [the guide to enable debug mode](https://currents.dev/readme/integration-with-cypress/troubleshooting#enabling-debug-mode-for-cypress-cloud-1.9.0+)
+ - **šš» Include the full log šš»** - starting from running the command till receiving an error.
- Attach a link / file for long outputs.
Example:
- - Linux: `DEBUG=currents:*,cypress:* cypress-cloud run ...`
- - Windows: `cmd /V /C "set DEBUG=currents:*,cypress:* && cypress-cloud run ..."`
+ - `npx cypress-cloud run ... --cloud-debug`
+
+ **Remove any sensitive data.**
- **Be sure to remove any sensitive data.**
value: |
diff --git a/.github/README.md b/.github/README.md
index 3a3dd22f..918d1a8a 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,17 @@ 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);
+ video: true; // enable video for cypress@13+
+ async setupNodeEvents(on, config) {
+ const result = await cloudPlugin(on, config);
+ return result;
},
},
});
@@ -60,7 +90,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
@@ -89,7 +119,7 @@ module.exports = {
networkHeaders: {
"User-Agent": "Custom",
"x-ms-blob-type": "BlockBlob"
- }
+ },
e2e: {
batchSize: 3, // orchestration batch size for e2e tests (Currents only, read below)
},
@@ -132,7 +162,7 @@ The configuration variables will resolve as follows:
## Batched Orchestration
-This package uses its own orchestration and reporting protocol that is independent of cypress native implementation. The new [orchestration protocol](https://currents.dev/readme/integration-with-cypress/cypress-cloud#batched-orchestration) uses cypress in "offline" mode and allows batching multiple spec files for better efficiency. You can adjust the batching configuration in `currents.config.js` and use different values for e2e and component tests.
+This package uses its own orchestration and reporting protocol that is independent of cypress native implementation. The new [orchestration protocol]([https://currents.dev/readme/integration-with-cypress/cypress-cloud#batched-orchestration](https://currents.dev/readme/integration-with-cypress/cypress-cloud/batched-orchestration)) uses cypress in "offline" mode and allows batching multiple spec files for better efficiency. You can adjust the batching configuration in `currents.config.js` and use different values for e2e and component tests.
## API
@@ -167,48 +197,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, configAlt);
+ return result;
+ },
+ },
+});
+```
### Spec files discovery
diff --git a/.github/workflows/e2e-smoke-windows.yaml b/.github/e2e-smoke-windows.yaml
similarity index 100%
rename from .github/workflows/e2e-smoke-windows.yaml
rename to .github/e2e-smoke-windows.yaml
diff --git a/.github/workflows/e2e-exports.yml b/.github/workflows/e2e-exports.yml
index 4ea488ae..ac9ee190 100644
--- a/.github/workflows/e2e-exports.yml
+++ b/.github/workflows/e2e-exports.yml
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
- node-version: ["18", "16", "14"]
+ node-version: ["18", "20"]
steps:
- uses: actions/checkout@v3
@@ -20,7 +20,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- - name: Install npm (node14)
+ - name: Install npm
run: npm install -g npm@latest
- name: Install dependencies
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9dd661b7..8d96df29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
+## [1.9.6](https://github.com/currents-dev/cypress-cloud/compare/v1.9.5...v1.9.6) (2023-09-19)
+
+
+### Bug Fixes
+
+* add post-run warnings ([#180](https://github.com/currents-dev/cypress-cloud/issues/180)) [CSR-601] ([e8d9354](https://github.com/currents-dev/cypress-cloud/commit/e8d93540d1aefb1235c0d478f4b39920d3267b9e))
+
## [1.9.5](https://github.com/currents-dev/cypress-cloud/compare/v1.9.4...v1.9.5) (2023-09-13)
diff --git a/examples/webapp/cypress.config.ts b/examples/webapp/cypress.config.ts
index f50a4805..39e2aada 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 32bd2596..36c49908 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 7fcee08f..ddc938f7 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 4cc6a584..69c5abd6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -113,6 +113,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",
@@ -7005,6 +7006,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",
@@ -9591,6 +9597,14 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/irregular-plurals": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz",
+ "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-absolute": {
"version": "1.0.0",
"license": "MIT",
@@ -15716,7 +15730,7 @@
}
},
"packages/cypress-cloud": {
- "version": "1.9.5",
+ "version": "1.9.6",
"license": "GPL-3.0-or-later",
"dependencies": {
"@cypress/commit-info": "^2.2.0",
@@ -15735,6 +15749,7 @@
"lil-http-terminator": "^1.2.3",
"lodash": "^4.17.21",
"nanoid": "^3.3.4",
+ "plur": "^4.0.0",
"pretty-ms": "^7.0.1",
"source-map-support": "^0.5.21",
"table": "^6.8.1",
@@ -16729,6 +16744,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "packages/cypress-cloud/node_modules/plur": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz",
+ "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==",
+ "dependencies": {
+ "irregular-plurals": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"packages/cypress-cloud/node_modules/proxy-agent": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz",
@@ -21453,6 +21482,7 @@
"lodash": "^4.17.21",
"nanoid": "^3.3.4",
"nock": "^13.2.9",
+ "plur": "^4.0.0",
"pretty-ms": "^7.0.1",
"release-it": "^16.1.5",
"rimraf": "^3.0.2",
@@ -22149,6 +22179,14 @@
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
"dev": true
},
+ "plur": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz",
+ "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==",
+ "requires": {
+ "irregular-plurals": "^3.2.0"
+ }
+ },
"proxy-agent": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz",
@@ -22364,6 +22402,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",
@@ -23950,6 +23993,11 @@
"version": "1.1.8",
"dev": true
},
+ "irregular-plurals": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz",
+ "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ=="
+ },
"is-absolute": {
"version": "1.0.0",
"requires": {
@@ -27552,6 +27600,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": "*",
diff --git a/packages/cypress-cloud/bin/lib/program.ts b/packages/cypress-cloud/bin/lib/program.ts
index e694397f..0d976bc6 100644
--- a/packages/cypress-cloud/bin/lib/program.ts
+++ b/packages/cypress-cloud/bin/lib/program.ts
@@ -107,7 +107,7 @@ ${getLegalNotice()}
.addOption(
new Option(
"--cloud-config-file ",
- "Specify the config file for cypress-cloud, defaults to 'currents.config.js' and will be searched in the project root, unless an aboslue path is provided"
+ "Specify the config file for cypress-cloud, defaults to 'currents.config.js' and will be searched in the project root, unless an absolute path is provided"
).default(undefined)
)
.addOption(
diff --git a/packages/cypress-cloud/lib/artifacts.ts b/packages/cypress-cloud/lib/artifacts.ts
index ed6accc5..ca3dc478 100644
--- a/packages/cypress-cloud/lib/artifacts.ts
+++ b/packages/cypress-cloud/lib/artifacts.ts
@@ -2,10 +2,12 @@ import Debug from "debug";
import { ScreenshotArtifact, ScreenshotUploadInstruction } from "../types";
import { updateInstanceStdout } from "./api";
import { safe } from "./lang";
-import { warn } from "./log";
+import { dim } from "./log";
+import { ExecutionState } from "./state";
import { uploadImage, uploadJson, uploadVideo } from "./upload";
const debug = Debug("currents:artifacts");
interface UploadArtifacts {
+ executionState: ExecutionState;
videoPath: string | null;
videoUploadUrl?: string | null;
screenshots: ScreenshotArtifact[];
@@ -14,6 +16,7 @@ interface UploadArtifacts {
coverageFilePath?: string | null;
}
export async function uploadArtifacts({
+ executionState,
videoPath,
videoUploadUrl,
screenshots,
@@ -21,8 +24,6 @@ export async function uploadArtifacts({
coverageFilePath,
coverageUploadUrl,
}: UploadArtifacts) {
- // title("blue", "Uploading Results");
-
debug("uploading artifacts: %o", {
videoPath,
videoUploadUrl,
@@ -33,9 +34,8 @@ export async function uploadArtifacts({
});
const totalUploads =
- (videoPath ? 1 : 0) + screenshots.length + (coverageFilePath ? 1 : 0);
+ (videoPath ? 1 : 0) + screenshots.length + (coverageUploadUrl ? 1 : 0);
if (totalUploads === 0) {
- // info("Nothing to upload");
return;
}
@@ -43,7 +43,12 @@ export async function uploadArtifacts({
if (videoUploadUrl && videoPath) {
await safe(
uploadVideo,
- (e) => debug("failed uploading video %s. Error: %o", videoPath, e),
+ (e) => {
+ debug("failed uploading video %s. Error: %o", videoPath, e);
+ executionState.addWarning(
+ `Failed uploading video ${videoPath}.\n${dim(e)}`
+ );
+ },
() => debug("success uploading", videoPath)
)(videoPath, videoUploadUrl);
}
@@ -60,17 +65,23 @@ export async function uploadArtifacts({
screenshot,
screenshotUploadUrls
);
- warn("Cannot find upload url for screenshot: %s", screenshot.path);
+ executionState.addWarning(
+ `No upload URL for screenshot ${screenshot.path}`
+ );
return Promise.resolve();
}
return safe(
uploadImage,
- (e) =>
+ (e) => {
debug(
"failed uploading screenshot %s. Error: %o",
screenshot.path,
e
- ),
+ );
+ executionState.addWarning(
+ `Failed uploading screenshot ${screenshot.path}.\n${dim(e)}`
+ );
+ },
() => debug("success uploading", screenshot.path)
)(screenshot.path, url);
})
@@ -80,12 +91,18 @@ export async function uploadArtifacts({
if (coverageUploadUrl && coverageFilePath) {
await safe(
uploadJson,
- (e) =>
+ (e) => {
debug(
"failed uploading coverage file %s. Error: %o",
coverageFilePath,
e
- ),
+ );
+
+ executionState.addWarning(
+ `Failed uploading coverage file ${coverageFilePath}.\n${dim(e)}`
+ );
+ },
+
() => debug("success uploading", coverageFilePath)
)(coverageFilePath, coverageUploadUrl);
}
diff --git a/packages/cypress-cloud/lib/coverage/index.ts b/packages/cypress-cloud/lib/coverage/index.ts
index 5ad7cc86..be881ae6 100644
--- a/packages/cypress-cloud/lib/coverage/index.ts
+++ b/packages/cypress-cloud/lib/coverage/index.ts
@@ -1,6 +1,5 @@
-import { join } from "path";
import fs from "fs/promises";
-import { warn } from "../log";
+import { join } from "path";
export const getCoverageFilePath = async (
coverageFile = "./.nyc_output/out.json"
@@ -9,12 +8,14 @@ export const getCoverageFilePath = async (
try {
await fs.access(path);
- return path;
+ return {
+ path,
+ error: false,
+ };
} catch (error) {
- warn(
- 'Coverage file was not found at "%s". Coverage recording will be skipped.',
- path
- );
- return null;
+ return {
+ path,
+ error,
+ };
}
};
diff --git a/packages/cypress-cloud/lib/log.ts b/packages/cypress-cloud/lib/log.ts
index 6f0d6ac3..87d1f70e 100644
--- a/packages/cypress-cloud/lib/log.ts
+++ b/packages/cypress-cloud/lib/log.ts
@@ -4,6 +4,7 @@ import util from "util";
const log = (...args: unknown[]) => console.log(util.format(...args));
export const info = log;
+export const format = util.format;
export const withError = (msg: string) =>
chalk.bgRed.white(" ERROR ") + " " + msg;
@@ -37,3 +38,5 @@ export const gray = chalk.gray;
export const white = chalk.white;
export const magenta = chalk.magenta;
export const bold = chalk.bold;
+export const yellow = chalk.yellow;
+export const dim = chalk.dim;
diff --git a/packages/cypress-cloud/lib/results/uploadResults.ts b/packages/cypress-cloud/lib/results/uploadResults.ts
index 65955d7c..61443eac 100644
--- a/packages/cypress-cloud/lib/results/uploadResults.ts
+++ b/packages/cypress-cloud/lib/results/uploadResults.ts
@@ -10,15 +10,18 @@ import { uploadArtifacts, uploadStdoutSafe } from "../artifacts";
import { setCancellationReason } from "../cancellation";
import { getInitialOutput } from "../capture";
import { isCurrents } from "../env";
+import { ConfigState, ExecutionState } from "../state";
import { getInstanceResultPayload, getInstanceTestsPayload } from "./results";
const debug = Debug("currents:results");
export async function getReportResultsTask(
instanceId: string,
- results: CypressCommandLine.CypressRunResult,
+ configState: ConfigState,
+ executionState: ExecutionState,
stdout: string,
coverageFilePath?: string
) {
+ const results = executionState.getInstanceResults(configState, instanceId);
const run = results.runs[0];
if (!run) {
throw new Error("No run found in Cypress results");
@@ -41,6 +44,7 @@ export async function getReportResultsTask(
return Promise.all([
uploadArtifacts({
+ executionState,
videoUploadUrl,
videoPath: run.video,
screenshotUploadUrls,
diff --git a/packages/cypress-cloud/lib/run.ts b/packages/cypress-cloud/lib/run.ts
index 5e7cf209..1610f160 100644
--- a/packages/cypress-cloud/lib/run.ts
+++ b/packages/cypress-cloud/lib/run.ts
@@ -1,6 +1,7 @@
import "./init";
import Debug from "debug";
+import plur from "plur";
import { getLegalNotice } from "../legal";
import { CurrentsRunParameters } from "../types";
import { createRun } from "./api";
@@ -12,12 +13,13 @@ import {
preprocessParams,
validateParams,
} from "./config";
+import { getCoverageFilePath } from "./coverage";
import { runBareCypress } from "./cypress";
import { activateDebug } from "./debug";
import { isCurrents } from "./env";
import { getGitInfo } from "./git";
import { setAPIBaseUrl, setRunId } from "./httpClient";
-import { bold, divider, info, spacer, title } from "./log";
+import { bold, dim, divider, info, spacer, title, warn, yellow } from "./log";
import { getPlatform } from "./platform";
import { pubsub } from "./pubsub";
import { summarizeTestResults, summaryTable } from "./results";
@@ -30,7 +32,6 @@ import { shutdown } from "./shutdown";
import { getSpecFiles } from "./specMatcher";
import { ConfigState, ExecutionState } from "./state";
import { startWSS } from "./ws";
-import { getCoverageFilePath } from "./coverage";
const debug = Debug("currents:run");
@@ -144,7 +145,10 @@ export async function run(params: CurrentsRunParameters = {}) {
title("white", "Cloud Run Finished");
console.log(summaryTable(_summary));
- info("š Recorded Run:", bold(run.runUrl));
+
+ printWarnings(executionState);
+
+ info("\nš Recorded Run:", bold(run.runUrl));
await shutdown();
@@ -176,15 +180,34 @@ function listenToSpecEvents(
debug("after:spec %o %o", spec, results);
executionState.setSpecAfter(spec.relative, results);
executionState.setSpecOutput(spec.relative, getCapturedOutput());
+
if (experimentalCoverageRecording) {
- const coverageFilePath = await getCoverageFilePath(
+ const { path, error } = await getCoverageFilePath(
config?.env?.coverageFile
);
- if (coverageFilePath) {
- executionState.setSpecCoverage(spec.relative, coverageFilePath);
+ if (!error) {
+ executionState.setSpecCoverage(spec.relative, path);
+ } else {
+ executionState.addWarning(
+ `Could not process coverage file "${path}"\n${dim(error)}`
+ );
}
}
createReportTaskSpec(configState, executionState, spec.relative);
}
);
}
+
+function printWarnings(executionState: ExecutionState) {
+ const warnings = Array.from(executionState.getWarnings());
+ if (warnings.length > 0) {
+ warn(
+ `${warnings.length} ${plur(
+ "Warning",
+ warnings.length
+ )} encountered during the execution:\n${warnings
+ .map((w, i) => `\n${yellow(`[${i + 1}/${warnings.length}]`)} ${w}`)
+ .join("\n")}`
+ );
+ }
+}
diff --git a/packages/cypress-cloud/lib/runner/reportTask.ts b/packages/cypress-cloud/lib/runner/reportTask.ts
index 5f7dcc0d..6be3bae8 100644
--- a/packages/cypress-cloud/lib/runner/reportTask.ts
+++ b/packages/cypress-cloud/lib/runner/reportTask.ts
@@ -29,7 +29,8 @@ export const createReportTask = (
reportTasks.push(
getReportResultsTask(
instanceId,
- executionState.getInstanceResults(configState, instanceId),
+ configState,
+ executionState,
instance.output ?? "no output captured",
instance.coverageFilePath
).catch(error)
diff --git a/packages/cypress-cloud/lib/state/execution.ts b/packages/cypress-cloud/lib/state/execution.ts
index 50a8a083..e149112b 100644
--- a/packages/cypress-cloud/lib/state/execution.ts
+++ b/packages/cypress-cloud/lib/state/execution.ts
@@ -26,8 +26,17 @@ type InstanceExecutionState = {
};
export class ExecutionState {
+ private warnings = new Set();
private state: Record = {};
+ public getWarnings() {
+ return this.warnings;
+ }
+
+ public addWarning(warning: string) {
+ this.warnings.add(warning);
+ }
+
public getResults(configState: ConfigState) {
return Object.values(this.state).map((i) =>
this.getInstanceResults(configState, i.instanceId)
diff --git a/packages/cypress-cloud/package.json b/packages/cypress-cloud/package.json
index e357709f..77327e15 100644
--- a/packages/cypress-cloud/package.json
+++ b/packages/cypress-cloud/package.json
@@ -1,6 +1,6 @@
{
"name": "cypress-cloud",
- "version": "1.9.5",
+ "version": "1.9.6",
"main": "./dist/index.js",
"author": "Currents Software Inc",
"homepage": "https://github.com/currents-dev/cypress-cloud",
@@ -69,6 +69,7 @@
"lil-http-terminator": "^1.2.3",
"lodash": "^4.17.21",
"nanoid": "^3.3.4",
+ "plur": "^4.0.0",
"pretty-ms": "^7.0.1",
"source-map-support": "^0.5.21",
"table": "^6.8.1",