diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index dd0f2f7a23..d3a1c8fa42 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -30,7 +30,7 @@ mainBuildFilters: &mainBuildFilters - /^release\/\d+\.\d+\.\d+$/ # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - 'update-v8-snapshot-cache-on-develop' - - 'emily/before-spec-promise' + - 'matth/misc/telemetry' # usually we don't build Mac app - it takes a long time # but sometimes we want to really confirm we are doing the right thing @@ -41,7 +41,6 @@ macWorkflowFilters: &darwin-workflow-filters - 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', << pipeline.git.branch >> ] - - equal: [ 'emily/before-spec-promise', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -52,7 +51,6 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters - 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', << pipeline.git.branch >> ] - - equal: [ 'fix/preflight', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -73,7 +71,6 @@ windowsWorkflowFilters: &windows-workflow-filters # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'lmiller/fixing-vite-windows', << pipeline.git.branch >> ] - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'fix/preflight', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -139,7 +136,7 @@ commands: - run: name: Check current branch to persist artifacts command: | - if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "emily/before-spec-promise" && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" ]]; then + if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" ]]; then echo "Not uploading artifacts or posting install comment for this branch." circleci-agent step halt fi @@ -495,6 +492,8 @@ commands: if [[ -v MAIN_RECORD_KEY ]]; then # internal PR CYPRESS_RECORD_KEY=$MAIN_RECORD_KEY \ + CYPRESS_INTERNAL_ENABLE_TELEMETRY="true" \ + OTEL_RESOURCE_ATTRIBUTES="ci.branch=$CIRCLE_BRANCH,ci.job=$CIRCLE_JOB,ci.node-index=$CIRCLE_NODE_INDEX,ci.circle=$CIRCLECI,ci.build-url=$CIRCLE_BUILD_URL,ci.build-number=$CIRCLE_BUILD_NUM" \ yarn cypress:run --record --parallel --group 5x-driver-<> --browser <> else # external PR @@ -567,6 +566,8 @@ commands: PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_WORKSPACE_ID \ PERCY_ENABLE=${PERCY_TOKEN:-0} \ PERCY_PARALLEL_TOTAL=-1 \ + CYPRESS_INTERNAL_ENABLE_TELEMETRY="true" \ + OTEL_RESOURCE_ATTRIBUTES="ci.branch=$CIRCLE_BRANCH,ci.job=$CIRCLE_JOB,ci.node-index=$CIRCLE_NODE_INDEX,ci.circle=$CIRCLECI,ci.build-url=$CIRCLE_BUILD_URL,ci.build-number=$CIRCLE_BUILD_NUM" \ $cmd yarn workspace @packages/<> cypress:run:<> --browser <> --record --parallel --group <>-<> else # external PR @@ -1307,7 +1308,7 @@ jobs: <<: *defaultsParameters steps: - restore_cached_workspace - - run: + - run: name: 'Determine if Release Workflow should be triggered' command: | if [[ "$CIRCLE_BRANCH" != "develop" ]]; then @@ -1455,7 +1456,7 @@ jobs: # run type checking for each individual package - run: yarn lerna run types - verify-mocha-results: - expectedResultCount: 18 + expectedResultCount: 19 - store_test_results: path: /tmp/cypress # CLI tests generate HTML files with sample CLI command output diff --git a/.github/workflows/stale_issues_and_pr_cleanup.yml b/.github/workflows/stale_issues_and_pr_cleanup.yml index 5cae94f8f7..4124117eb7 100644 --- a/.github/workflows/stale_issues_and_pr_cleanup.yml +++ b/.github/workflows/stale_issues_and_pr_cleanup.yml @@ -5,7 +5,7 @@ on: debug-only: description: 'debug-only' required: false - default: true + default: false days-before-stale: description: 'days-before-stale' required: false @@ -50,5 +50,5 @@ jobs: exempt-issue-labels: ${{ github.event.inputs.exempt-issue-labels || env.DEFAULT_EXEMPT_ISSUE_LABELS }} exempt-pr-labels: ${{ github.event.inputs.exempt-pr-labels || env.DEFAULT_EXEMPT_PR_LABELS }} exempt-all-milestones: true - operations-per-run: 1000 #using during debug mode to capture all the tickets impacted + operations-per-run: 200 #keeping this a bit higher because it processes newest tickets to oldest debug-only: ${{ github.event.inputs.debug-only || env.DEFAULT_DEBUG_ONLY }} diff --git a/.github/workflows/triage_add_to_project.yml b/.github/workflows/triage_add_to_project.yml index a96e0771af..7cb4b10585 100644 --- a/.github/workflows/triage_add_to_project.yml +++ b/.github/workflows/triage_add_to_project.yml @@ -37,7 +37,7 @@ jobs: echo 'IS_COLLABORATOR='$(jq -r '.data.repository.collaborators.totalCount' collaborators.json) >> $GITHUB_ENV - uses: actions/add-to-project@v0.4.1 # only add issues/prs from outside contributors to the project - if: ${{ env.IS_COLLABORATOR == 0 }} + if: ${{ env.IS_COLLABORATOR == 0 || github.event.repository.name == 'cypress-support-internal' || github.event.pull_request.user.login == 'github-actions[bot]' || github.event.issue.user.login == 'github-actions[bot]' }} with: project-url: https://github.com/orgs/${{github.repository_owner}}/projects/${{env.PROJECT_NUMBER}} github-token: ${{ secrets.ADD_TO_TRIAGE_BOARD_TOKEN }} diff --git a/.github/workflows/update_v8_snapshot_cache.yml b/.github/workflows/update_v8_snapshot_cache.yml index 60586ac441..61f06d52a3 100644 --- a/.github/workflows/update_v8_snapshot_cache.yml +++ b/.github/workflows/update_v8_snapshot_cache.yml @@ -30,7 +30,7 @@ jobs: strategy: max-parallel: 1 matrix: - platform: [ubuntu-latest, windows-latest, macos-latest] + platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} env: CYPRESS_BOT_APP_ID: ${{ secrets.RAM_APP }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eee1fe15ab..fedcaa775f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ Thanks for taking the time to contribute! :smile: ## Code of Conduct -All contributors are expecting to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). +All contributors are expected to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). ## Opening Issues @@ -112,7 +112,7 @@ video | Problems with video recordings | [open](https://github.com/cypress-io/cy ## Writing Documentation - + Cypress documentation lives in a separate repository with its own dependencies and build tools. See [Documentation Contributing Guidelines](https://github.com/cypress-io/cypress-documentation/blob/master/CONTRIBUTING.md). @@ -155,7 +155,7 @@ Here is a list of the core packages in this repository with a short description, | [proxy](./packages/proxy) | `@packages/proxy` | Code for Cypress' network proxy layer. | | [reporter](./packages/reporter) | `@packages/reporter` | The reporter shows the running results of the tests (The Command Log UI). | | [resolve-dist](./packages/resolve-dist) | `@packages/resolve-dist` | Centralizes the resolution of paths to compiled/static assets from server-side code.. | - | [rewriter](./packages/rewriter) | `@packages/rewriter` | The logic to rewrite JS and HTML that flows through the Cypress proxy. + | [rewriter](./packages/rewriter) | `@packages/rewriter` | The logic to rewrite JS and HTML that flows through the Cypress proxy. | [root](./packages/root) | `@packages/root` | Dummy package pointing at the root of the repository. | | [runner](./packages/runner) | `@packages/runner` | (deprecated) The runner is the minimal "chrome" around the user's application under test. | | [scaffold-config](./packages/scaffold-config) | `@packages/scaffold-config` | The logic related to scaffolding new projects using launchpad. | @@ -318,13 +318,13 @@ Each package is responsible for building itself and testing itself and can do so When executing top or package level scripts, [Vite](https://vitejs.dev/) may be used to build/host parts of the application. This section is to serve as a general reference for these environment variables that may be leverage throughout the repository. ###### `CYPRESS_INTERNAL_VITE_DEV` Set to `1` if wanting to leverage [vite's](https://vitejs.dev/guide/#command-line-interface) `vite dev` over `vite build` to avoid a full [production build](https://vitejs.dev/guide/build.html). -###### `CYPRESS_INTERNAL_VITE_INSPECT` +###### `CYPRESS_INTERNAL_VITE_INSPECT` Used internally to leverage [vite-plugin-inspect](https://github.com/antfu/vite-plugin-inspect) to view intermediary vite plugin state. The `CYPRESS_INTERNAL_VITE_DEV` is required for this to be applied correctly. Set to `1` to enable. -###### `CYPRESS_INTERNAL_VITE_OPEN_MODE_TESTING` +###### `CYPRESS_INTERNAL_VITE_OPEN_MODE_TESTING` Leveraged only for internal cy-in-cy type tests to access the Cypress instance from the parent frame. Please see the [E2E Open Mode Testing](./guides/e2e-open-testing.md) Guide. Set to `true` when doing -###### `CYPRESS_INTERNAL_VITE_APP_PORT` +###### `CYPRESS_INTERNAL_VITE_APP_PORT` Leveraged only when `CYPRESS_INTERNAL_VITE_DEV` is set to spawn the vite dev server for the app on the specified port. The default port is `3333`. -###### `CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT` +###### `CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT` Leveraged only when `CYPRESS_INTERNAL_VITE_DEV` is set to spawn the vite dev server for the launchpad on the specified port. The default port is `3001`. #### Debug Logs @@ -443,7 +443,7 @@ We do not continuously deploy the Cypress binary, so `develop` contains all of t - After the PR is approved, the original contributor can merge the PR (if the original contributor has access). - When you merge a PR into `develop`, select [**Squash and merge**](https://docs.github.com/en/github/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-pull-request-commits). This will squash all commits into a single commit. -*The only exceptions to squashing are:* +*The only exceptions to squashing are:* 1. When converting files to another language and there is a clear commit history needed to maintain from the file conversion. 2. When merging a `release/*` branch to `develop`. Individual PRs were already squashed when they were merged to the release branch, and we want that history intact on develop. diff --git a/browser-versions.json b/browser-versions.json index 6f10d1c49c..2da445bd2b 100644 --- a/browser-versions.json +++ b/browser-versions.json @@ -1,5 +1,5 @@ { - "chrome:beta": "112.0.5615.49", - "chrome:stable": "111.0.5563.146", + "chrome:beta": "113.0.5672.24", + "chrome:stable": "112.0.5615.49", "chrome:minimum": "64.0.3282.0" } diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 259c857613..fbb16480a6 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -3,14 +3,29 @@ _Released 04/11/2023 (PENDING)_ +**Features:** + +- The Component Testing setup wizard will now show a warning message if an issue is encountered with an installed [third party framework definition](https://on.cypress.io/component-integrations). Addresses [#25838](https://github.com/cypress-io/cypress/issues/25838). + **Bugfixes:** - Capture the [Azure](https://azure.microsoft.com/) CI provider's environment variable [`SYSTEM_PULLREQUEST_PULLREQUESTNUMBER`](https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#system-variables-devops-services) to display the linked PR number in the Cloud. Addressed in [#26215](https://github.com/cypress-io/cypress/pull/26215). - - Fixed an issue in the onboarding wizard where project framework & bundler would not be auto-detected when opening directly into component testing mode using the `--component` CLI flag. Fixes [#22777](https://github.com/cypress-io/cypress/issues/22777). + - Fixed an issue in the onboarding wizard where project framework & bundler would not be auto-detected when opening directly into component testing mode using the `--component` CLI flag. Fixes [#22777](https://github.com/cypress-io/cypress/issues/22777) and [#26388](https://github.com/cypress-io/cypress/issues/26388). + - Updated to use the `SEMAPHORE_GIT_WORKING_BRANCH` [Semphore](https://docs.semaphoreci.com) CI environment variable to correctly associate a Cloud run to the current branch. Previously this was incorrectly associating a run to the target branch. Fixes [#26309](https://github.com/cypress-io/cypress/issues/26309). + - Fix an edge case in Component Testing where a custom `baseUrl` in `tsconfig.json` for Next.js 13.2.0+ is not respected. This was partially fixed in [#26005](https://github.com/cypress-io/cypress/pull/26005), but an edge case was missed. Fixes [#25951](https://github.com/cypress-io/cypress/issues/25951). + - Correctly detect and resolve dependencies when configuring Component Testing in projects using Yarn's [Plug'n'Play feature](https://yarnpkg.com/features/pnp). Fixes [#25960](https://github.com/cypress-io/cypress/issues/25960). + - Fixed an issue where `click` events fired on `.type('{enter}')` did not propagate through shadow roots. Fixes [#26392](https://github.com/cypress-io/cypress/issues/26392). + +**Misc:** + +- Removed unintentional debug logs. Addressed in [#26411](https://github.com/cypress-io/cypress/pull/26411). +- Improved styling on the [Runs Page](https://docs.cypress.io/guides/core-concepts/cypress-app#Runs). Addresses [#26180](https://github.com/cypress-io/cypress/issues/26180). **Dependency Updates:** +- Upgraded [`commander`](https://www.npmjs.com/package/commander) from `^5.1.0` to `^6.2.1`. Addressed in [#26226](https://github.com/cypress-io/cypress/pull/26226). - Upgraded [`minimist`](https://www.npmjs.com/package/minimist) from `1.2.6` to `1.2.8` to address this [CVE-2021-44906](https://github.com/advisories/GHSA-xvch-5gv4-984h) NVD security vulnerability. Addressed in [#26254](https://github.com/cypress-io/cypress/pull/26254). +- Added [`better-sqlite3`](https://github.com/WiseLibs/better-sqlite3) to support storing test run information which will be sent when recording to the Cloud **Misc:** - Removed unintentional debug logs. Address in [#26411](https://github.com/cypress-io/cypress/pull/26411) @@ -35,10 +50,6 @@ _Released 03/28/2023_ **Misc:** - Made some minor styling updates to the Debug page. Addresses [#26041](https://github.com/cypress-io/cypress/issues/26041). - -**Dependency Updates:** - - - Added [`better-sqlite3`](https://github.com/WiseLibs/better-sqlite3) to support storing test run information which will be sent when recording to the Cloud ## 12.8.1 diff --git a/cli/__snapshots__/cli_spec.js b/cli/__snapshots__/cli_spec.js index 562cbeb7fd..36d6ee4f98 100644 --- a/cli/__snapshots__/cli_spec.js +++ b/cli/__snapshots__/cli_spec.js @@ -218,7 +218,7 @@ exports['cli help command shows help 1'] = ` Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. @@ -258,7 +258,7 @@ exports['cli help command shows help for -h 1'] = ` Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. @@ -298,7 +298,7 @@ exports['cli help command shows help for --help 1'] = ` Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. @@ -339,7 +339,7 @@ exports['cli unknown command shows usage and exits 1'] = ` Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. @@ -408,20 +408,6 @@ Electron version: not found Bundled Node version: not found ` -exports['cli --version no binary version 1'] = ` -Cypress package version: 1.2.3 -Cypress binary version: not installed -Electron version: not found -Bundled Node version: not found -` - -exports['cli -v no binary version 1'] = ` -Cypress package version: 1.2.3 -Cypress binary version: not installed -Electron version: not found -Bundled Node version: not found -` - exports['cli cypress run warns with space-separated --spec 1'] = ` ⚠ Warning: It looks like you're passing --spec a space-separated list of arguments: @@ -466,7 +452,7 @@ exports['cli CYPRESS_INTERNAL_ENV allows and warns when staging environment 1'] Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. diff --git a/cli/lib/cli.js b/cli/lib/cli.js index 04599c93fc..248a7d72a1 100644 --- a/cli/lib/cli.js +++ b/cli/lib/cli.js @@ -154,26 +154,16 @@ const text = (description) => { function includesVersion (args) { return ( - _.includes(args, 'version') || _.includes(args, '--version') || _.includes(args, '-v') ) } -function showVersions (args) { +function showVersions (opts) { debug('printing Cypress version') - debug('additional arguments %o', args) + debug('additional arguments %o', opts) - const versionParser = commander.option( - '--component ', 'component to report version for', - ) - .allowUnknownOption(true) - const parsed = versionParser.parse(args) - const parsedOptions = { - component: parsed.component, - } - - debug('parsed version arguments %o', parsedOptions) + debug('parsed version arguments %o', opts) const reportAllVersions = (versions) => { logger.always('Cypress package version:', versions.package) @@ -215,8 +205,8 @@ function showVersions (args) { return require('./exec/versions') .getVersions() .then((versions = defaultVersions) => { - if (parsedOptions.component) { - reportComponentVersion(parsedOptions.component, versions) + if (opts?.component) { + reportComponentVersion(opts.component, versions) } else { reportAllVersions(versions) } @@ -456,13 +446,19 @@ module.exports = { program.help() }) - program + const handleVersion = (cmd) => { + return cmd + .option('--component ', 'component to report version for') + .action((opts, ...other) => { + showVersions(util.parseOpts(opts)) + }) + } + + handleVersion(program + .storeOptionsAsProperties() .option('-v, --version', text('version')) .command('version') - .description(text('version')) - .action(() => { - showVersions(args) - }) + .description(text('version'))) maybeAddInspectFlags(addCypressOpenCommand(program)) .action((opts) => { @@ -665,7 +661,7 @@ module.exports = { // and now does not understand top level options // .option('-v, --version').command('version') // so we have to manually catch '-v, --version' - return showVersions(args) + handleVersion(program) } debug('program parsing arguments') diff --git a/cli/package.json b/cli/package.json index f5dec38660..ec12f4cbd1 100644 --- a/cli/package.json +++ b/cli/package.json @@ -34,7 +34,7 @@ "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", - "commander": "^5.1.0", + "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", @@ -89,7 +89,6 @@ "mock-fs": "5.1.1", "mocked-env": "1.3.2", "nock": "13.2.9", - "postinstall-postinstall": "2.1.0", "proxyquire": "2.1.3", "resolve-pkg": "2.0.0", "shelljs": "0.8.5", diff --git a/cli/test/lib/cli_spec.js b/cli/test/lib/cli_spec.js index bf6ebf97cb..e688125f26 100644 --- a/cli/test/lib/cli_spec.js +++ b/cli/test/lib/cli_spec.js @@ -23,11 +23,11 @@ describe('cli', () => { beforeEach(() => { logger.reset() - sinon.stub(process, 'exit') + sinon.stub(process, 'exit').returns(null) os.platform.returns('darwin') - // sinon.stub(util, 'exit') - sinon.stub(util, 'logErrorExit1') + sinon.stub(util, 'logErrorExit1').returns(null) + sinon.stub(util, 'pkgBuildInfo').returns({ stable: true }) this.exec = (args) => { const cliArgs = `node test ${args}`.split(' ') @@ -136,189 +136,169 @@ describe('cli', () => { }) }) - context('cypress version', () => { - let restoreEnv + ;['--version', '-v', 'version'].forEach((versionCommand) => { + context(`cypress ${versionCommand}`, () => { + let restoreEnv - afterEach(() => { - if (restoreEnv) { - restoreEnv() - restoreEnv = null - } - }) + afterEach(() => { + if (restoreEnv) { + restoreEnv() + restoreEnv = null + } + }) - const binaryDir = '/binary/dir' + const binaryDir = '/binary/dir' - beforeEach(() => { - sinon.stub(state, 'getBinaryDir').returns(binaryDir) - }) - - describe('individual package versions', () => { beforeEach(() => { + sinon.stub(state, 'getBinaryDir').returns(binaryDir) + }) + + describe('individual package versions', () => { + beforeEach(() => { + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon + .stub(state, 'getBinaryPkgAsync') + .withArgs(binaryDir) + .resolves({ + version: 'X.Y.Z', + electronVersion: '10.9.8', + electronNodeVersion: '7.7.7', + }) + }) + + it('reports just the package version', (done) => { + this.exec(`${versionCommand} --component package`) + process.exit.callsFake((exitCode) => { + expect(logger.print()).to.equal('1.2.3') + done() + }) + }) + + it('reports just the binary version', (done) => { + this.exec(`${versionCommand} --component binary`) + process.exit.callsFake(() => { + expect(logger.print()).to.equal('X.Y.Z') + done() + }) + }) + + it('reports just the electron version', (done) => { + this.exec(`${versionCommand} --component electron`) + process.exit.callsFake(() => { + expect(logger.print()).to.equal('10.9.8') + done() + }) + }) + + it('reports just the bundled Node version', (done) => { + this.exec(`${versionCommand} --component node`) + process.exit.callsFake(() => { + expect(logger.print()).to.equal('7.7.7') + done() + }) + }) + + it('handles not found bundled Node version', (done) => { + state.getBinaryPkgAsync + .withArgs(binaryDir) + .resolves({ + version: 'X.Y.Z', + electronVersion: '10.9.8', + }) + + this.exec(`${versionCommand} --component node`) + process.exit.callsFake(() => { + expect(logger.print()).to.equal('not found') + done() + }) + }) + }) + + it('reports package version', (done) => { sinon.stub(util, 'pkgVersion').returns('1.2.3') sinon .stub(state, 'getBinaryPkgAsync') .withArgs(binaryDir) .resolves({ version: 'X.Y.Z', - electronVersion: '10.9.8', - electronNodeVersion: '7.7.7', }) - }) - it('reports just the package version', (done) => { - this.exec('version --component package') + this.exec(versionCommand) process.exit.callsFake(() => { - expect(logger.print()).to.equal('1.2.3') + snapshot('cli version and binary version 1', logger.print(), { allowSharedSnapshot: true }) done() }) }) - it('reports just the binary version', (done) => { - this.exec('version --component binary') + it('reports package and binary message', (done) => { + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' }) + + this.exec(versionCommand) process.exit.callsFake(() => { - expect(logger.print()).to.equal('X.Y.Z') + snapshot('cli version and binary version 2', logger.print(), { allowSharedSnapshot: true }) done() }) }) - it('reports just the electron version', (done) => { - this.exec('version --component electron') - process.exit.callsFake(() => { - expect(logger.print()).to.equal('10.9.8') - done() - }) - }) - - it('reports just the bundled Node version', (done) => { - this.exec('version --component node') - process.exit.callsFake(() => { - expect(logger.print()).to.equal('7.7.7') - done() - }) - }) - - it('handles not found bundled Node version', (done) => { - state.getBinaryPkgAsync - .withArgs(binaryDir) - .resolves({ + it('reports electron and node message', (done) => { + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z', - electronVersion: '10.9.8', + electronVersion: '10.10.88', + electronNodeVersion: '11.10.3', }) - this.exec('version --component node') + this.exec(versionCommand) process.exit.callsFake(() => { - expect(logger.print()).to.equal('not found') + snapshot('cli version with electron and node 1', logger.print(), { allowSharedSnapshot: true }) done() }) }) - }) - it('reports package version', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon - .stub(state, 'getBinaryPkgAsync') - .withArgs(binaryDir) - .resolves({ - version: 'X.Y.Z', + it('reports package and binary message with npm log silent', (done) => { + restoreEnv = mockedEnv({ + npm_config_loglevel: 'silent', + }) + + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' }) + + this.exec(versionCommand) + process.exit.callsFake(() => { + // should not be empty! + snapshot('cli version and binary version with npm log silent', logger.print(), { allowSharedSnapshot: true }) + done() + }) }) - this.exec('version') - process.exit.callsFake(() => { - snapshot('cli version and binary version 1', logger.print()) - done() - }) - }) + it('reports package and binary message with npm log warn', (done) => { + restoreEnv = mockedEnv({ + npm_config_loglevel: 'warn', + }) - it('reports package and binary message', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' }) + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves({ + version: 'X.Y.Z', + }) - this.exec('version') - process.exit.callsFake(() => { - snapshot('cli version and binary version 2', logger.print()) - done() - }) - }) - - it('reports electron and node message', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves({ - version: 'X.Y.Z', - electronVersion: '10.10.88', - electronNodeVersion: '11.10.3', - }) - - this.exec('version') - process.exit.callsFake(() => { - snapshot('cli version with electron and node 1', logger.print()) - done() - }) - }) - - it('reports package and binary message with npm log silent', (done) => { - restoreEnv = mockedEnv({ - npm_config_loglevel: 'silent', - }) - - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' }) - - this.exec('version') - process.exit.callsFake(() => { + this.exec(versionCommand) + process.exit.callsFake(() => { // should not be empty! - snapshot('cli version and binary version with npm log silent', logger.print()) - done() - }) - }) - - it('reports package and binary message with npm log warn', (done) => { - restoreEnv = mockedEnv({ - npm_config_loglevel: 'warn', + snapshot('cli version and binary version with npm log warn', logger.print(), { allowSharedSnapshot: true }) + done() + }) }) - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves({ - version: 'X.Y.Z', - }) + it('handles non-existent binary', (done) => { + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves(null) - this.exec('version') - process.exit.callsFake(() => { - // should not be empty! - snapshot('cli version and binary version with npm log warn', logger.print()) - done() - }) - }) - - it('handles non-existent binary version', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves(null) - - this.exec('version') - process.exit.callsFake(() => { - snapshot('cli version no binary version 1', logger.print()) - done() - }) - }) - - it('handles non-existent binary --version', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves(null) - - this.exec('--version') - process.exit.callsFake(() => { - snapshot('cli --version no binary version 1', logger.print()) - done() - }) - }) - - it('handles non-existent binary -v', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves(null) - - this.exec('-v') - process.exit.callsFake(() => { - snapshot('cli -v no binary version 1', logger.print()) - done() + this.exec(versionCommand) + process.exit.callsFake(() => { + snapshot('cli version no binary version 1', logger.print(), { allowSharedSnapshot: true }) + done() + }) }) }) }) diff --git a/npm/create-cypress-tests/CHANGELOG.md b/npm/create-cypress-tests/CHANGELOG.md index db00f727d0..3b2f80df83 100644 --- a/npm/create-cypress-tests/CHANGELOG.md +++ b/npm/create-cypress-tests/CHANGELOG.md @@ -1,3 +1,5 @@ +# [create-cypress-tests-v2.0.2](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.1...create-cypress-tests-v2.0.2) (2023-04-07) + # [create-cypress-tests-v2.0.1](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.0...create-cypress-tests-v2.0.1) (2023-01-03) diff --git a/npm/create-cypress-tests/package.json b/npm/create-cypress-tests/package.json index 969dff7c97..e52091e85c 100644 --- a/npm/create-cypress-tests/package.json +++ b/npm/create-cypress-tests/package.json @@ -20,7 +20,7 @@ "bluebird": "3.7.2", "chalk": "4.1.0", "cli-highlight": "2.1.10", - "commander": "6.1.0", + "commander": "6.2.1", "fast-glob": "3.2.7", "find-up": "5.0.0", "fs-extra": "^9.1.0", diff --git a/npm/grep/README.md b/npm/grep/README.md index 4675a83c88..b9bdd13ad0 100644 --- a/npm/grep/README.md +++ b/npm/grep/README.md @@ -19,8 +19,6 @@ All other tests will be marked pending, see why in the [Cypress test statuses](h If you have multiple spec files, all specs will be loaded, and every test will be filtered the same way, since the grep is run-time operation and cannot eliminate the spec files without loading them. If you want to run only specific tests, use the built-in [--spec](https://on.cypress.io/command-line#cypress-run-spec-lt-spec-gt) CLI argument. -Watch the video [intro to @cypress/grep plugin](https://www.youtube.com/watch?v=HS-Px-Sghd8) - Table of Contents @@ -602,4 +600,4 @@ Version >= 3 of @cypress/grep _only_ supports Cypress >= 10. License: MIT - do anything with the code, but don't blame me if it does not work. Support: if you find any problems with this module, email / tweet / -[open issue](https://github.com/cypress-io/cypress/issues) on Github. \ No newline at end of file +[open issue](https://github.com/cypress-io/cypress/issues) on Github. diff --git a/npm/webpack-dev-server/CHANGELOG.md b/npm/webpack-dev-server/CHANGELOG.md index b2b8530335..63a5dcd8bd 100644 --- a/npm/webpack-dev-server/CHANGELOG.md +++ b/npm/webpack-dev-server/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@cypress/webpack-dev-server-v3.4.1](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v3.4.0...@cypress/webpack-dev-server-v3.4.1) (2023-04-07) + + +### Bug Fixes + +* correctly pass resolvedBaseUrl for Next.js 13.2.0+ ([#26399](https://github.com/cypress-io/cypress/issues/26399)) ([e8390f4](https://github.com/cypress-io/cypress/commit/e8390f46cd852417f2c0b07a9c73eeaf7437e823)) + # [@cypress/webpack-dev-server-v3.4.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v3.3.1...@cypress/webpack-dev-server-v3.4.0) (2023-03-20) diff --git a/npm/webpack-dev-server/src/helpers/nextHandler.ts b/npm/webpack-dev-server/src/helpers/nextHandler.ts index b1e9a5b632..ebd747cbea 100644 --- a/npm/webpack-dev-server/src/helpers/nextHandler.ts +++ b/npm/webpack-dev-server/src/helpers/nextHandler.ts @@ -29,7 +29,12 @@ export async function nextHandler (devServerConfig: WebpackDevServerConfig): Pro */ function getNextJsPackages (devServerConfig: WebpackDevServerConfig) { const resolvePaths = { paths: [devServerConfig.cypressConfig.projectRoot] } - const packages = {} as { loadConfig: Function, getNextJsBaseWebpackConfig: Function, nextLoadJsConfig: Function } + const packages = {} as { + loadConfig: (phase: 'development', dir: string) => Promise + getNextJsBaseWebpackConfig: Function + nextLoadJsConfig: Function + getSupportedBrowsers: (dir: string, isDevelopment: boolean, nextJsConfig: any) => Promise + } try { const loadConfigPath = require.resolve('next/dist/server/config', resolvePaths) @@ -55,6 +60,15 @@ function getNextJsPackages (devServerConfig: WebpackDevServerConfig) { throw new Error(`Failed to load "next/dist/build/load-jsconfig" with error: ${ e.message ?? e}`) } + // Does not exist prior to Next 13. + try { + const getUtilsPath = require.resolve('next/dist/build/utils', resolvePaths) + + packages.getSupportedBrowsers = require(getUtilsPath).getSupportedBrowsers ?? (() => Promise.resolve([])) + } catch (e: any) { + throw new Error(`Failed to load "next/dist/build/utils" with error: ${ e.message ?? e}`) + } + return packages } @@ -173,12 +187,13 @@ function getNextJsPackages (devServerConfig: WebpackDevServerConfig) { ] */ async function loadWebpackConfig (devServerConfig: WebpackDevServerConfig): Promise { - const { loadConfig, getNextJsBaseWebpackConfig, nextLoadJsConfig } = getNextJsPackages(devServerConfig) + const { loadConfig, getNextJsBaseWebpackConfig, nextLoadJsConfig, getSupportedBrowsers } = getNextJsPackages(devServerConfig) const nextConfig = await loadConfig('development', devServerConfig.cypressConfig.projectRoot) const runWebpackSpan = getRunWebpackSpan(devServerConfig) const reactVersion = getReactVersion(devServerConfig.cypressConfig.projectRoot) const jsConfigResult = await nextLoadJsConfig?.(devServerConfig.cypressConfig.projectRoot, nextConfig) + const supportedBrowsers = await getSupportedBrowsers(devServerConfig.cypressConfig.projectRoot, true, nextConfig) const webpackConfig = await getNextJsBaseWebpackConfig( devServerConfig.cypressConfig.projectRoot, @@ -196,8 +211,12 @@ async function loadWebpackConfig (devServerConfig: WebpackDevServerConfig): Prom compilerType: 'client', // Required for Next.js > 13 hasReactRoot: reactVersion === 18, - // Required for Next.js > 13.2.1 to respect TS/JS config + // Required for Next.js > 13.2.0 to respect TS/JS config jsConfig: jsConfigResult.jsConfig, + // Required for Next.js > 13.2.0 to respect tsconfig.compilerOptions.baseUrl + resolvedBaseUrl: jsConfigResult.resolvedBaseUrl, + // Added in Next.js 13, passed via `...info`: https://github.com/vercel/next.js/pull/45637/files + supportedBrowsers, }, ) diff --git a/package.json b/package.json index 2216e86159..fc1f7f755a 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "stop-only": "npx stop-only --skip .cy,.publish,.projects,node_modules,dist,dist-test,fixtures,lib,bower_components,src,__snapshots__ --exclude cypress-tests.ts,*only.cy.js", "stop-only-all": "yarn stop-only --folder packages", "pretest": "yarn ensure-deps", - "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,data-context,electron,errors,extension,https-proxy,launcher,net-stubbing,network,packherd-require,proxy,rewriter,scaffold-config,socket,v8-snapshot-require}'\" --scope \"'@tooling/{electron-mksnapshot,v8-snapshot}'\"", + "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,data-context,electron,errors,extension,https-proxy,launcher,net-stubbing,network,packherd-require,proxy,rewriter,scaffold-config,socket,v8-snapshot-require,telemetry}'\" --scope \"'@tooling/{electron-mksnapshot,v8-snapshot}'\"", "test-debug": "lerna exec yarn test-debug --ignore \"'@packages/{driver,root,static,web-config}'\"", "pretest-e2e": "yarn ensure-deps", "test-integration": "lerna exec yarn test-integration --ignore \"'@packages/{driver,root,static,web-config}'\"", @@ -189,7 +189,6 @@ "patch-package": "6.4.7", "playwright-webkit": "1.24.2", "pluralize": "8.0.0", - "postinstall-postinstall": "2.0.0", "print-arch": "1.0.0", "proxyquire": "2.1.3", "rimraf": "3.0.2", diff --git a/packages/app/cypress/e2e/runs.cy.ts b/packages/app/cypress/e2e/runs.cy.ts index 0cdce03ba5..e64a589d97 100644 --- a/packages/app/cypress/e2e/runs.cy.ts +++ b/packages/app/cypress/e2e/runs.cy.ts @@ -100,7 +100,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.contains('a', 'OVERLIMIT').click() cy.withCtx((ctx) => { - expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.eq('http://dummy.cypress.io/runs/4') + expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.contain('http://dummy.cypress.io/runs/4') }) }) }) @@ -660,22 +660,22 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.visitApp() moveToRunsPage() - cy.get('[href="http://dummy.cypress.io/runs/0"]').first().within(() => { + cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().within(() => { cy.findByText('fix: make gql work CANCELLED') cy.get('[data-cy="run-card-icon-CANCELLED"]') }) - cy.get('[href="http://dummy.cypress.io/runs/1"]').first().within(() => { + cy.get('[href^="http://dummy.cypress.io/runs/1"]').first().within(() => { cy.findByText('fix: make gql work ERRORED') cy.get('[data-cy="run-card-icon-ERRORED"]') }) - cy.get('[href="http://dummy.cypress.io/runs/2"]').first().within(() => { + cy.get('[href^="http://dummy.cypress.io/runs/2"]').first().within(() => { cy.findByText('fix: make gql work FAILED') cy.get('[data-cy="run-card-icon-FAILED"]') }) - cy.get('[href="http://dummy.cypress.io/runs/0"]').first().as('firstRun') + cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().as('firstRun') cy.get('@firstRun').within(() => { cy.get('[data-cy="run-card-author"]').contains('John Appleseed') @@ -699,7 +699,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.get('[data-cy^="runCard-"]').first().click() cy.withCtx((ctx) => { - expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.eq('http://dummy.cypress.io/runs/0') + expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.contain('http://dummy.cypress.io/runs/0') }) }) diff --git a/packages/app/index.d.ts b/packages/app/index.d.ts index c37b64a87b..9f093be679 100644 --- a/packages/app/index.d.ts +++ b/packages/app/index.d.ts @@ -13,9 +13,9 @@ export {} * To work around this, we build the driver, eventManager * and some other dependencies using webpack, and consumed the dist'd * source code. - * + * * This is attached to `window` under the `UnifiedRunner` namespace. - * + * * For now, just declare the types that we need to give us type safety where possible. * Eventually, we should decouple the event manager and import it directly. */ @@ -37,7 +37,7 @@ declare global { * We get a reference to the copy of React (and React DOM) * that is used in the Reporter and Driver, which are bundled with * webpack. - * + * * Unfortunately, attempting to have React in a project * using Vue causes mad conflicts because React'S JSX type * is ambient, so we cannot actually type it. @@ -54,7 +54,7 @@ declare global { * Any React components or general code needed from * runner, reporter or driver are also bundled with * webpack and made available via the window.UnifedRunner namespace. - * + * * We cannot import the correct types, because this causes the linter and type * checker to run on runner and reporter, and it blows up. */ @@ -64,4 +64,4 @@ declare global { } } } -} \ No newline at end of file +} diff --git a/packages/app/package.json b/packages/app/package.json index 3cccd49a23..c47831ff31 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -29,6 +29,7 @@ "@iconify/vue": "3.0.0-beta.1", "@intlify/vite-plugin-vue-i18n": "2.4.0", "@packages/frontend-shared": "0.0.0-development", + "@packages/telemetry": "0.0.0-development", "@percy/cypress": "^3.1.0", "@popperjs/core": "2.11.6", "@testing-library/cypress": "9.0.0", diff --git a/packages/app/src/debug/DebugCommitIcon.vue b/packages/app/src/debug/DebugCommitIcon.vue index 1dc4c8cafb..73aa83bfb7 100644 --- a/packages/app/src/debug/DebugCommitIcon.vue +++ b/packages/app/src/debug/DebugCommitIcon.vue @@ -1,6 +1,21 @@ \ No newline at end of file + diff --git a/packages/app/src/debug/DebugCurrentRunIcon.vue b/packages/app/src/debug/DebugCurrentRunIcon.vue index 4590bef142..e086241075 100644 --- a/packages/app/src/debug/DebugCurrentRunIcon.vue +++ b/packages/app/src/debug/DebugCurrentRunIcon.vue @@ -1,5 +1,17 @@ \ No newline at end of file + diff --git a/packages/app/src/debug/DebugRunNavigation.cy.tsx b/packages/app/src/debug/DebugRunNavigation.cy.tsx index 3dca32d8c4..dd5cc4859e 100644 --- a/packages/app/src/debug/DebugRunNavigation.cy.tsx +++ b/packages/app/src/debug/DebugRunNavigation.cy.tsx @@ -163,7 +163,10 @@ describe('', () => { }) it('renders correctly in several sizes', () => { + //pausing time to prevent Percy flake + cy.clock(new Date()) cy.get('[data-cy="debug-toggle"]').click() + cy.tick(2 * 1000) //allow toggle to animate cy.viewport(616, 850) //currently the narrowest the parent component will go cy.percySnapshot('narrowest') diff --git a/packages/app/src/main.ts b/packages/app/src/main.ts index ded76864c6..b5ad310ff9 100644 --- a/packages/app/src/main.ts +++ b/packages/app/src/main.ts @@ -11,15 +11,23 @@ import { createPinia } from './store' import Toast, { POSITION } from 'vue-toastification' import 'vue-toastification/dist/index.css' import { createWebsocket, getRunnerConfigFromWindow } from './runner' +import { telemetry } from '@packages/telemetry/src/browser' + +// Grab the time just before loading config to include that in the cypress:app span +const now = performance.now() +const config = getRunnerConfigFromWindow() + +telemetry.init({ namespace: 'cypress:app', config }) +telemetry.startSpan({ name: 'cypress:app', attachType: 'root', opts: { startTime: now } }) const app = createApp(App) -const config = getRunnerConfigFromWindow() - const ws = createWebsocket(config) window.ws = ws +telemetry.attachWebSocket(ws) + // This injects the Cypress driver and Reporter, which are bundled with Webpack. // No need to wait for it to finish - it's resolved async with a deferred promise, // So it'll be ready when we need to run a spec. If not, we will wait for it. diff --git a/packages/app/src/runner/event-manager.ts b/packages/app/src/runner/event-manager.ts index c437159ea6..05414298ec 100644 --- a/packages/app/src/runner/event-manager.ts +++ b/packages/app/src/runner/event-manager.ts @@ -13,6 +13,8 @@ import { useScreenshotStore } from '../store/screenshot-store' import { useStudioStore } from '../store/studio-store' import { getAutIframeModel } from '.' import { handlePausing } from './events/pausing' +import { addTelemetryListeners } from './events/telemetry' +import { telemetry } from '@packages/telemetry/src/browser' export type CypressInCypressMochaEvent = Array>> @@ -129,6 +131,12 @@ export class EventManager { window.location.href = url }) + this.ws.on('update:telemetry:context', (contextString) => { + const context = JSON.parse(contextString) + + telemetry.setRootContext(context) + }) + this.ws.on('automation:push:message', (msg, data = {}) => { if (!Cypress) return @@ -346,6 +354,7 @@ export class EventManager { // that Cypress knows not to set any more // cookies $window.on('beforeunload', () => { + telemetry.getSpan('cypress:app')?.end() this.reporterBus.emit('reporter:restart:test:run') this._clearAllCookies() @@ -452,6 +461,8 @@ export class EventManager { } _addListeners () { + addTelemetryListeners(Cypress) + Cypress.on('message', (msg, data, cb) => { this.ws.emit('client:request', msg, data, cb) }) diff --git a/packages/app/src/runner/events/telemetry.ts b/packages/app/src/runner/events/telemetry.ts new file mode 100644 index 0000000000..37088c959b --- /dev/null +++ b/packages/app/src/runner/events/telemetry.ts @@ -0,0 +1,39 @@ +import { telemetry } from '@packages/telemetry/src/browser' + +export const addTelemetryListeners = (Cypress) => { + Cypress.on('test:before:run', (attributes, test) => { + // we emit the 'test:before:run' events within various driver tests + try { + // If a span for a previous test hasn't been ended, end it before starting the new test span + const previousTestSpan = telemetry.findActiveSpan((span) => { + return span.name.startsWith('test:') + }) + + if (previousTestSpan) { + telemetry.endActiveSpanAndChildren(previousTestSpan) + } + + const span = telemetry.startSpan({ name: `test:${test.fullTitle()}`, active: true }) + + span?.setAttributes({ + currentRetry: attributes.currentRetry, + }) + } catch (error) { + // TODO: log error when client side debug logging is available + } + }) + + Cypress.on('test:after:run', (attributes, test) => { + try { + const span = telemetry.getSpan(`test:${test.fullTitle()}`) + + span?.setAttributes({ + timings: JSON.stringify(attributes.timings), + }) + + span?.end() + } catch (error) { + // TODO: log error when client side debug logging is available + } + }) +} diff --git a/packages/app/src/runs/RunCard.vue b/packages/app/src/runs/RunCard.vue index 8a4a715c2f..30e9898fa8 100644 --- a/packages/app/src/runs/RunCard.vue +++ b/packages/app/src/runs/RunCard.vue @@ -1,15 +1,19 @@