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

Add ability to configure ‘upload’ and ‘storage’ of videos separately when test suite passes/fails. #2522

Closed
GrayedFox opened this issue Sep 25, 2018 · 75 comments
Labels
cli stage: proposal 💡 No work has been done of this issue topic: video 📹 type: enhancement Requested enhancement of existing feature

Comments

@GrayedFox
Copy link

GrayedFox commented Sep 25, 2018

Current behavior:

Currently, it's only possible to switch the video feature on or off in a binary way.

Desired behavior:

To be able to record and process videos only when a test fails. If there's no way to do this (due to the recording feature not knowing whether a test will fail or not when it begins to record) - the processing part could at least be tweaked so that only failed tests have their videos processed and saved, whereas passing tests are technically recorded, but never processed or saved to disk if the test passes.

Versions

Cypress 3.1.0
Chrome 69
Electron 59

@drewbrend
Copy link

@GrayedFox This already exists, but the docs are not clear. #2217

@GrayedFox
Copy link
Author

GrayedFox commented Sep 25, 2018

And for those who want to disable uploading entirely and still save failed spec videos?

I don't think it makes a lot of sense to bundle this into a single config switch - I can certainly see a use case where a user has a parallel setup and doesn't want to upload any videos, but still wants failed videos to be saved on some machine somewhere. Uploading and recording are two very different things, and I'm now (at the very least) the second person to not understand that the videoUploadOnPasses partly covers this wanted behaviour.

Having two switches makes it super clear:

"uploadVideos": true | false
"recordVideos": true | false | fail | pass

Now, I can save only passing or failing videos, both, or none, and Cypress will upload whatever videos happen to be there.

Edit: I've thrown in "pass" as an option here too, since once a working "only failed tests" switch is working, negating this value based on the config should be trivial, and gives users added functionality

@drewbrend
Copy link

I agree that it leaves some functionality to be desired, but the current behaviour seems to do what you and I are asking for.

@GrayedFox

This comment has been minimized.

@drewbrend
Copy link

Check here: https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/modes/run.coffee#L716

@plemarquand
Copy link

@GrayedFox I like your proposal for recordVideos. Currently videos are generated for every suite on my build server but really I only want to keep the ones for failed suites as build artifacts, discarding the videos for passing suites to save space on the build server.

As it stands right now setting videoUploadOnPasses: false is actually worse for my situation, since videos of passing suites are still recorded and saved to disk uncompressed.

@GrayedFox

This comment has been minimized.

@kjellski

This comment has been minimized.

@GrayedFox
Copy link
Author

GrayedFox commented Dec 1, 2018

@brian-mann in response to your comment here - where you ask why this feature is necessary, I think this thread highlights valid use cases, perhaps?

Also I would argue that rather than asking the contributors and users of Cypress why they want to be able to control how they use Cypress, I would ask (aside from having the video recording have default values that you can build other features on top of it): is the video recording feature really so tightly coupled with dashboard functionality that it cannot be disabled without breaking the dashboard?

I'd need to take a better look at the code but that sounds like we may be breaking the single responsibility principle here. Ideally, the dashboard functions as a graphical interface for whatever pluggable features the Cypress user thinks they need... even if you know better and think that real life use cases would necessitate that all people should be using feature X or Y, that doesn't seem to me to be a reason to have such tightly coupled code. That, and using something like feature flags for the dash also means you can rapidly disable any feature which (god forbid!) ships with a serious bug, while working on a hotfix, without disabling other parts of the system that work (just food for thought!).

Thanks again for steering this wonderful product through the murky waters of open source shenanigans - I'm sure comments like these ones bring you immense happiness and joy 🙇

@kjellski
Copy link

kjellski commented Dec 3, 2018

I'm completely on your page @GrayedFox, very thankful for the cypress project itself and would not be testing my frontends like this if it wouldn't be there...

@brian-mann @jennifer-shehane let's focus on a solution here, the other ticket is closed and probably won't get much attention for others following along currently.

For reference, here are the last two comments on my deprecated issue, cited:

@brian-mann:

@kjellski I'm coming back to this now, and the answer may be in front of me - but I'm confused still as to why this is necessary. As of today, you don't need to record to the dashboard to get value out of the video. The option videoUploadOnPasses only affects when --record. If you're not using the Dashboard then you can manage the video yourself since the videos still be physically written to disk.

I must be missing something. Your comment here: #2652 (comment) suggests that you're not using the Dashboard, and I just want to make sure we're on the same page that you can completely manage the video yourself if that's the case without us needing to change anything on our end.

@kjellski:

Thanks for getting involved again @brian-mann! So the problem in general is this:
AFAIK you can not use video recording without sending it out to your servers via upload.

So you would be able to only record videos, without upload? I mean that's what I'm after... but it's not possible to use --record without a key to the dashboard and then it uploads... maybe I'm missing some documentation that suggested this... :D

Ameobea added a commit to Ameobea/cypress-documentation that referenced this issue Mar 7, 2019
Add a note about how the `videoUploadOnPasses` option can be used to avoid processing and uploading videos in the case that all tests pass.

Partially addresses cypress-io/cypress#2217, cypress-io/cypress#2522
jennifer-shehane added a commit to cypress-io/cypress-documentation that referenced this issue Mar 8, 2019
* Update screenshots-and-videos.md

Add a note about how the `videoUploadOnPasses` option can be used to avoid processing and uploading videos in the case that all tests pass.

Partially addresses cypress-io/cypress#2217, cypress-io/cypress#2522

* Update screenshots-and-videos.md

Remove autoformatter-caused changes

* Update to clarify that videos process per spec file run

- '+' to 'and'


Co-authored-by: Jennifer Shehane <shehane.jennifer@gmail.com>
@dialex

This comment has been minimized.

@marinakuranda
Copy link

This feature would be very appreciated.
Ideally, it would follow the same logic as with saving screenshots - only saving them on fail.

@jennifer-shehane jennifer-shehane changed the title Feature request: only record videos on test fail Add ability to configure 'upload' and 'compression' of videos (outside of actual recording) Oct 22, 2019
@seyfer

This comment has been minimized.

@Alados

This comment has been minimized.

@kjellski

This comment has been minimized.

@dialex
Copy link

dialex commented Mar 3, 2020

Well @kjellski, my cypress.json file has that exact configuration (no projectId, "video": true, "videoUploadOnPasses": false) and still Cypress generates videos for all tests, which is not what we want.

To reiterate, we want the ability to record videos only for failed tests. A possible implementation was described at #2522 (comment):

"uploadVideos": true | false
"recordVideos": true | false | fail | pass

I think it's clear by now that the community wants this feature. The specification was already laid out by @GrayedFox. Is there anything blocking the implementation of this feature?

@kjellski
Copy link

kjellski commented Mar 3, 2020

@dialex aaahhhh, you only want the ones for failed! Sorry - I was not expecting this restriction - just as a workaround, you could just delete the successfull ones with a little script before ending your 'build' for now...

But yes, this seems to be not existing yet AFAIK!

@josefsmrz

This comment has been minimized.

@liambutler
Copy link

In my company, I'm currently working to help other teams to move on to Cypress. Our setup runs Cypress on Jenkins hosts, and are orchestrated with Cypress Dashboard. The videos are automatically saved in Jenkins. There is no way to get around this, other than completely disabling video in the configuration.

One of the current issues hindering wider adoption is how much space these videos take up per Jenkins Job, so disabling compression isn't an option for us.

While deleting the videos after the fact would help with this space issue, the videos will still be saved in the first instance. I'd like to increase the parallelisation on our cypress tests, but we are currently running into i/o bottlenecks on Jenkins. Saving then deleting in quick succession will not help with this

Deleting a saved video in an after step, while helpful for saving space, feels more like a bandaid to me. Given that there are already some video configuration options, I would rather that these be fleshed out.

@rooby
Copy link

rooby commented Oct 23, 2020

@liambutler

While deleting the videos after the fact would help with this space issue, the videos will still be saved in the first instance.

aside from disabling videos entirely, how would you expect to solve your problem?
The video has to be created before knowing if the test will fail or not, so it has the be deleted after the fact.

The only thing I can think of that would reduce space, is to ensure that you can hook into the after spec as each spec is run and not only right at the end of all tests, so if all the tests pass the most space that would be used is the space of the largest single video file.

@jennifer-shehane
The proposed solution seems acceptable, although it would be a nice-to-have for videos and screenshots to have equivalent configuration options (not really necessary though, just seems more symmetrical 😆).
There would need to be documentation on how to implement the hook, since it would be a common requirement.

@liambutler
Copy link

liambutler commented Oct 23, 2020

@rooby

aside from disabling videos entirely, how would you expect to solve your problem?

Given that there is a saving/compression step after each spec, I am assuming that the videos are in memory until they are saved to disk. Is that not the case?

Edit- thanks also for the helpful suggestion :)

@GrayedFox
Copy link
Author

@liambutler if you really want to only record videos for failed tests, the solution would be to run all failed tests twice (and hope you get the same failure again, which in my experience is likely, but this will cause headaches for flaky tests/envs).

You could set up a default config where videos are disabled entirely, and then you identify each failed test (easy enough to do after the fact or during runtime). Save the name of each failed test and construct a comma delimited string and then re-run only those failed tests with videos enabled, so with a cypress run --spec "$commaDelimitedFailedTestsString" --record --tag "failed" . Will cost a bit more time but if space and running tests in parallel are your main concerns, you might actually see a slight performance boost in the initial run since videos are skipped entirely during the first run, and I assume a healthy codebase on your end means you have a fail rate of less than, say, 10% 😉

@jennifer-shehane out of interest when exactly does that after hook trigger?

  1. before the video is saved to disk or encoded (it's just been recorded and exists in some raw format in memory)
  2. after the video is written to disk, but before it is encoded
  3. after the video is written to disk and after it is encoded

If it's either at step 1 or 2 I believe this proposal solves my (and most others) concerns. If it's at step 3, then it only helps solve the space issue, since it allows for a delete operation after each passing spec instead of at the end of the test suite.

@liambutler
Copy link

Still hoping that the solution I had in mind is possible, but thanks for all the tips! I love how helpful the Cypress community is toward each other

@jennifer-shehane
Copy link
Member

Related PRs to this feature:

@jennifer-shehane
Copy link
Member

jennifer-shehane commented Dec 22, 2020

In Cypress 6.2.0, you can now listen to after:spec events in the plugins file when setting the experimentalRunEvents configuration option to true.

This means that you can delete the video after a spec has run for any reason - including if all of the tests passed.

This alone will not skip the processing/compression of the video. If you want to skip the processing time for the video, you can set the videoCompression to false, so that the compression/processing time will be skipped.

To skip processing time for all videos:

cypress.json

{
  "videoCompression": false
}

To delete a video if all tests pass:

cypress.json

{
  "experimentalRunEvents": true
}

`cypress/plugins/index.js

// 'npm install -D del' - https://www.npmjs.com/package/del
const del = require('del')

module.exports = (on, config) => {
  on('after:spec', (spec, results) => {
    if (results.stats.failures === 0 && results.video) {
      // `del()` returns a promise, so it's important to return it to ensure
      // deleting the video is finished before moving on
      return del(results.video)
    }
  })
}

Let us know if you have any feedback on this experimental feature.

@GrayedFox
Copy link
Author

Unless people have specific concerns with how this issue is resolved using the experimental feature, I believe the core concerns have been addressed. Thank you to @jennifer-shehane for your continued patience and persistence in getting this resolved and navigating both the external and internal communications channels, and to all those who used this thread constructively.

As with many a good thing, they can and do take time.

@nichtmeinweg
Copy link

tried to use the approved solution but getting "fast-glob\out\readers\sync.js: Unexpected token" on "cypress": "^6.2.1". Anything I miss?

In Cypress 6.2.0, you can now listen to after:spec events in the plugins file when setting the experimentalRunEvents configuration option to true.

This means that you can delete the video after a spec has run for any reason - including if all of the tests passed.

This alone will not skip the processing/compression of the video. If you want to skip the processing time for the video, you can set the videoCompression to false, so that the compression/processing time will be skipped.

To skip processing time for all videos:

cypress.json

{
  "videoCompression": false
}

To delete a video if all tests pass:

cypress.json

{
  "experimentalRunEvents": true
}

`cypress/plugins/index.js

// 'npm install -D del' - https://www.npmjs.com/package/del
const del = require('del')

module.exports = (on, config) => {
  on('after:spec', (spec, results) => {
    if (results.stats.failures === 0 && results.video) {
      // `del()` returns a promise, so it's important to return it to ensure
      // deleting the video is finished before moving on
      return del(result.video)
    }
  })
}

Let us know if you have any feedback on this experimental feature.

@jennifer-shehane
Copy link
Member

@nichtmeinweg I did mispell results in the original workaround. I've updated my comment, but this should not have given this error. Can you provide a reproducible example?

@nichtmeinweg
Copy link

nichtmeinweg commented Jan 12, 2021

@nichtmeinweg I did mispell results in the original workaround. I've updated my comment, but this should not have given this error. Can you provide a reproducible example?

Thanks a lot for the correction, my error is most probably related to the needed upgrade of cypress I had to do for following the workaround.

[2]
[2] Oops...we found an error preparing this test file:
[2]
[2]   **cypress\support\index.js**
[2]
[2] The error was:
[2]
[2] Error: Parsing file C:\repo\patientstrength_KOS-447\ps-client-ekogu\ClientApp\node_modules\fast-glob\out\readers\stream.js: Unexpected token (16:10)

Which version of "del" are you using?

it is not related to the flag: "experimentalRunEvents", or the EVENT function, its actually this line that causes the error:

const del = require('del');

Dependencies:

 "dependencies": {
    "@coreui/coreui": "^2.1.16",
    "@coreui/react": "^2.5.8",
    "@date-io/date-fns": "^1.3.13",
    "@fortawesome/fontawesome-svg-core": "^1.2.28",
    "@fortawesome/free-solid-svg-icons": "^5.13.0",
    "@fortawesome/react-fontawesome": "^0.1.9",
    "@material-ui/core": "^4.10.2",
    "@material-ui/icons": "^4.9.1",
    "@material-ui/lab": "^4.0.0-alpha.56",
    "@material-ui/pickers": "^3.2.10",
    "@microsoft/applicationinsights-react-js": "^3.0.1",
    "@microsoft/applicationinsights-web": "^2.5.6",
    "@testing-library/jest-dom": "^5.11.5",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "bootstrap": "^4.4.1",
    "chart.js": "^2.9.3",
    "classnames": "^2.2.6",
    "core-js": "^3.6.5",
    "cross-env": "^7.0.2",
    "cypress-commands": "^1.1.0",
    "cypress-file-upload": "^4.1.1",
    "date-fns": "^2.12.0",
    "enzyme": "^3.11.0",
    "enzyme-adapter-react-16": "^1.15.2",
    "history": "^5.0.0",
    "html-to-react": "^1.4.3",
    "i18next": "^19.4.4",
    "i18next-http-backend": "^1.0.8",
    "jquery": "^3.5.1",
    "jwt-decode": "^2.2.0",
    "lodash": "4.17.20",
    "mailslurp-client": "^10.0.4",
    "msal": "^1.4.3",
    "node-sass": "^4.14.1",
    "npm": "^6.14.7",
    "prop-types": "^15.7.2",
    "react": "^17.0.1",
    "react-aad-msal": "^2.3.5",
    "react-app-polyfill": "^1.0.6",
    "react-bootstrap": "^1.0.1",
    "react-dom": "^17.0.1",
    "react-dropzone": "^11.0.2",
    "react-i18next": "^11.4.0",
    "react-icd10": "^1.0.7",
    "react-icons": "^3.10.0",
    "react-js-banner": "^0.5.2",
    "react-keys": "^3.0.0-rc20",
    "react-live-clock": "^4.0.5",
    "react-overlay-component": "^1.0.2",
    "react-phone-input-2": "^2.13.3",
    "react-phone-number-input": "^3.0.22",
    "react-redux": "^7.2.0",
    "react-router-dom": "^5.1.2",
    "react-scripts": "^4.0.0",
    "react-scroll": "^1.8.0",
    "react-stepper-horizontal": "^1.0.11",
    "react-textarea-autosize": "^8.0.1",
    "react-window": "^1.8.5",
    "reactstrap": "^8.4.1",
    "redux": "^4.0.5",
    "redux-form": "^8.3.5",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.3.0",
    "web-vitals": "^0.2.4"
  },
 "devDependencies": {
    "@babel/core": "7.11.4",
    "@babel/plugin-proposal-class-properties": "7.12.1",
    "@bahmutov/cy-api": "1.4.2",
    "@cypress/code-coverage": "^3.9.1",
    "@cypress/instrument-cra": "^1.4.0",
    "@cypress/webpack-preprocessor": "^5.5.0",
    "@storybook/addon-actions": "6.0.22",
    "@storybook/addon-essentials": "6.0.22",
    "@storybook/addon-links": "6.0.22",
    "@storybook/node-logger": "6.0.22",
    "@storybook/preset-create-react-app": "3.1.4",
    "@storybook/react": "6.0.22",
    "@types/jest": "26.0.14",
    "@types/node": "14.11.2",
    "@types/react": "16.9.54",
    "@types/react-dom": "16.9.9",
    "babel-loader": "8.1.0",
    "babel-plugin-istanbul": "6.0.0",
    "chalk": "4.1.0",
    "check-code-coverage": "1.9.3",
    "console-log-div": "0.6.3",
    "cypress": "^6.2.1",
    "cypress-xpath": "^1.6.1",
    "del": "^6.0.0",
    "electron": "10.1.5",
    "eslint": "7.14.0",
    "eslint-config-google": "0.14.0",
    "eslint-plugin-only-warn": "1.0.2",
    "eslint-plugin-react": "7.21.5",
    "express": "4.17.1",
    "fluent-ffmpeg": "^2.1.2",
    "istanbul-merge": "1.1.1",
    "jest-junit": "11.1.0",
    "lint-staged": "10.5.2",
    "markdown-link-check": "3.8.1",
    "mocha-junit-reporter": "2.0.0",
    "nyc": "15.1.0",
    "parcel-bundler": "1.12.4",
    "prettier": "1.19.1",
    "semantic-release": "^17.3.1",
    "serve": "11.3.2",
    "start-server-and-test": "^1.11.7",
    "storybook-addon-jsx": "7.3.4",
    "typescript": "3.8.3",
    "yarn": "1.22.7"
  }

@caseyjhol
Copy link
Contributor

@jennifer-shehane

This alone will not skip the processing/compression of the video. If you want to skip the processing time for the video, you can set the videoCompression to false, so that the compression/processing time will be skipped.

Doesn't this disable compression for all videos then? I'd still like to compress videos for failed tests.

@josefsmrz
Copy link

@jennifer-shehane

This alone will not skip the processing/compression of the video. If you want to skip the processing time for the video, you can set the videoCompression to false, so that the compression/processing time will be skipped.

Doesn't this disable compression for all videos then? I'd still like to compress videos for failed tests.

It does. You can set "videoUploadOnPasses": false to disable compression only for passing tests.

@caseyjhol
Copy link
Contributor

caseyjhol commented Jan 12, 2021

@josefsmrz That's only for Cypress videos uploaded to the Dashboard, though. It seems like deleting the video via the after:spec event prevents processing by default (#9595), so I think videoCompression: false can be avoided (but perhaps I'm wrong).

Edit: good catch, looks like it does disable processing:

if (videoCompression === false || shouldUploadVideo === false) {
.

@josefsmrz
Copy link

@josefsmrz That's only for Cypress videos uploaded to the Dashboard, though. It seems like deleting the video via the after:spec event prevents processing by default (#9595), so I think videoCompression: false can be avoided (but perhaps I'm wrong).

@caseyjhol I guess it is intended mainly for use with the Dashboard, but as a side effect, the setting actually does what I've described even when not using the Dashboard. It has been discussed in the very beginning of this thread

@kaiyoma
Copy link

kaiyoma commented Jan 25, 2021

Let us know if you have any feedback on this experimental feature.

@jennifer-shehane This seems to work very well for us. We were running out of space on our CI server and large videos were the main culprit. In addition to freeing up lots of space, we save valuable time by not processing the video too. The rare fix that saves both time and space! 😄

@kaiyoma
Copy link

kaiyoma commented Jan 26, 2021

Is anyone else noticing (after implementing the suggestion here) that now there are warnings about missing video files? I'm seeing this for all my tests now:

18:53:39  Warning: We could not find the video at the following path, so we were unable to process it.
18:53:39  
18:53:39  Video path: .../basic/data.spec.js.mp4
18:53:39  
18:53:39  This error will not alter the exit code.

Is there any way to silence these warnings?

@josefsmrz
Copy link

Is anyone else noticing (after implementing the suggestion here) that now there are warnings about missing video files? I'm seeing this for all my tests now:

18:53:39  Warning: We could not find the video at the following path, so we were unable to process it.
18:53:39  
18:53:39  Video path: .../basic/data.spec.js.mp4
18:53:39  
18:53:39  This error will not alter the exit code.

Is there any way to silence these warnings?

yeah also got them

@jnv
Copy link

jnv commented Feb 5, 2021

I get the same error about missing videos. Perhaps one possibility to avoid this is to collect file paths and delete videos in after:run event handler. I have adjusted the original code to something like:

import {rm} from 'fs/promises'

const pluginConfig = (on, config) => {
	const filesToDelete = []
	on('after:spec', (spec, results) => {
		if (results.stats.failures === 0 && results.video) {
			filesToDelete.push(results.video)
		}
	})
	on('after:run', async () => {
		console.log(
			'Deleting %d videos from successful specs',
			filesToDelete.length
		)
		await Promise.all(filesToDelete.map((videoFile) => rm(videoFile)))
	})
}

export default pluginConfig

Not that this will delete videos only when ran through cypress run. Also unless you set either videoCompression, or videoUploadOnPasses options to false, the videos will be compressed only to be deleted which is wasteful.

Also, this particular snippet won't work with Node 12 currently bundled by Cypress due to lack of fs/promises module. I will leave this part as an exercise to reader. 😉

@NickRumenov
Copy link

Yes, that works without the error message, I just changed the "after:run" hook with:

on('after:run', async () => {

    filesToDelete.forEach(path => {
      fs.unlink(path, (err) => {
        if (err) {
          console.error(err)
          return
        }
        console.log('Successfully deleted passing test videos' + path)
      })
    })
  })

NodeJS 14.5.1
Hopefully, we will have an official solution soon. Thanks

@edaldridge
Copy link

I've added this to my current setup, minus the experimental features part and the video compression which I believe are not needed for this to work fully now.

The process works locally for me but when I pushed the new plugin/index.js to my repo and ran a test from Jenkins I am getting an error about the plugin/index.js file not being found (see below). I am guessing this is more than likely a config error on Jenkins rather than something related to the improvement suggested here but I am hoping someone else may have experienced this issue and may have a solution?

The plugins file is missing or invalid.

Your pluginsFile is set to /opt/jenkins/workspace/<path to test>/cypress/plugins/index.js, but either the file is missing, it contains a syntax error, or threw an error when required. The pluginsFile must be a .js, .ts, or .coffee file.

Or you might have renamed the extension of your pluginsFile. If that's the case, restart the test runner.

Please fix this, or set pluginsFile to false if a plugins file is not necessary for your project.

Error: Cannot find module 'del'

@jennifer-shehane
Copy link
Member

@kaiyoma We've updated the 'warning' message for when the video is missing for processing. Since this is likely expected behavior that someone coded in (to delete the video), we replaced the messaging with a single line so that you are still informed that the video processing is being skipped. #15828

No video found after spec ran - skipping processing. Video path: /my/videos

@jennifer-shehane
Copy link
Member

The experimentalRunEvents configuration is no longer necessary to get this 'after:spec' listener. It is built into the product directly.

This issue will be closed to further comment as the exact issue feature here is delivered. See #2522 (comment) for an example.

If you're experiencing a bug related to the issue above in Cypress, please open a new issue with a fully reproducible example that we can run. There may be a specific edge case with the issue that we need more detail to fix.

@cypress-io cypress-io locked as resolved and limited conversation to collaborators Apr 27, 2021
@jennifer-shehane
Copy link
Member

Uploaded this comment to show solution for controlling which videos to delete in newer versions: https://docs.cypress.io/guides/guides/screenshots-and-videos#Control-which-videos-to-keep-and-upload-to-Cypress-Cloud

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cli stage: proposal 💡 No work has been done of this issue topic: video 📹 type: enhancement Requested enhancement of existing feature
Projects
None yet
Development

No branches or pull requests