From 6df7a709c37a9a27aa60c3503e513c20634b70bb Mon Sep 17 00:00:00 2001 From: Adam Stone-Lord Date: Mon, 22 May 2023 19:59:27 +0000 Subject: [PATCH 01/13] chore: capture versions of relevant dependencies with `x-dependencies` header (#26814) --- .../src/sources/UtilDataSource.ts | 6 +- .../src/sources/VersionsDataSource.ts | 75 +++++++++---------- .../unit/sources/VersionsDataSource.spec.ts | 42 +++++++---- .../launchpad/cypress/e2e/open-mode.cy.ts | 26 ++++++- packages/scaffold-config/src/dependencies.ts | 75 +++++++++++++++++++ packages/scaffold-config/src/frameworks.ts | 31 ++++++++ .../test/unit/frameworks.spec.ts | 58 ++++++++++++++ 7 files changed, 255 insertions(+), 58 deletions(-) create mode 100644 packages/scaffold-config/test/unit/frameworks.spec.ts diff --git a/packages/data-context/src/sources/UtilDataSource.ts b/packages/data-context/src/sources/UtilDataSource.ts index b17a716c21e5..7ccdf22fd9dc 100644 --- a/packages/data-context/src/sources/UtilDataSource.ts +++ b/packages/data-context/src/sources/UtilDataSource.ts @@ -1,6 +1,6 @@ import fetch from 'cross-fetch' import type { DataContext } from '../DataContext' -import { isDependencyInstalled } from '@packages/scaffold-config' +import { isDependencyInstalled, isDependencyInstalledByName } from '@packages/scaffold-config' // Require rather than import since data-context is stricter than network and there are a fair amount of errors in agent. const { agent } = require('@packages/network') @@ -23,4 +23,8 @@ export class UtilDataSource { isDependencyInstalled (dependency: Cypress.CypressComponentDependency, projectPath: string) { return isDependencyInstalled(dependency, projectPath) } + + isDependencyInstalledByName (packageName: string, projectPath: string) { + return isDependencyInstalledByName(packageName, projectPath) + } } diff --git a/packages/data-context/src/sources/VersionsDataSource.ts b/packages/data-context/src/sources/VersionsDataSource.ts index 15b0c301aa10..88cc16f7d254 100644 --- a/packages/data-context/src/sources/VersionsDataSource.ts +++ b/packages/data-context/src/sources/VersionsDataSource.ts @@ -3,8 +3,7 @@ import type { DataContext } from '..' import type { TestingType } from '@packages/types' import { CYPRESS_REMOTE_MANIFEST_URL, NPM_CYPRESS_REGISTRY_URL } from '@packages/types' import Debug from 'debug' -import { WIZARD_DEPENDENCIES } from '@packages/scaffold-config' -import semver from 'semver' +import { dependencyNamesToDetect } from '@packages/scaffold-config' const debug = Debug('cypress:data-context:sources:VersionsDataSource') @@ -161,45 +160,43 @@ export class VersionsDataSource { } } - try { - const projectPath = this.ctx.currentProject - - if (projectPath) { - const dependenciesToCheck = WIZARD_DEPENDENCIES - - debug('Checking %d dependencies in project', dependenciesToCheck.length) - // Check all dependencies of interest in parallel - const dependencyResults = await Promise.allSettled( - dependenciesToCheck.map(async (dependency) => { - const result = await this.ctx.util.isDependencyInstalled(dependency, projectPath) - - // If a dependency isn't satisfied then we are no longer interested in it, - // exclude from further processing by rejecting promise - if (!result.satisfied) { - throw new Error('Unsatisfied dependency') - } - - // We only want major version, fallback to `-1` if we couldn't detect version - const majorVersion = result.detectedVersion ? semver.major(result.detectedVersion) : -1 - - // For any satisfied dependencies, build a `package@version` string - return `${result.dependency.package}@${majorVersion}` - }), - ) - // Take any dependencies that were found and combine into comma-separated string - const headerValue = dependencyResults - .filter(this.isFulfilled) - .map((result) => result.value) - .join(',') - - if (headerValue) { - manifestHeaders['x-dependencies'] = headerValue + if (this._initialLaunch) { + try { + const projectPath = this.ctx.currentProject + + if (projectPath) { + debug('Checking %d dependencies in project', dependencyNamesToDetect.length) + // Check all dependencies of interest in parallel + const dependencyResults = await Promise.allSettled( + dependencyNamesToDetect.map(async (dependency) => { + const result = await this.ctx.util.isDependencyInstalledByName(dependency, projectPath) + + if (!result.detectedVersion) { + throw new Error(`Could not resolve dependency version for ${dependency}`) + } + + // For any satisfied dependencies, build a `package@version` string + return `${result.dependency}@${result.detectedVersion}` + }), + ) + + // Take any dependencies that were found and combine into comma-separated string + const headerValue = dependencyResults + .filter(this.isFulfilled) + .map((result) => result.value) + .join(',') + + if (headerValue) { + manifestHeaders['x-dependencies'] = headerValue + } + } else { + debug('No project path, skipping dependency check') } - } else { - debug('No project path, skipping dependency check') + } catch (err) { + debug('Failed to detect project dependencies', err) } - } catch (err) { - debug('Failed to detect project dependencies', err) + } else { + debug('Not initial launch of Cypress, skipping dependency check') } try { diff --git a/packages/data-context/test/unit/sources/VersionsDataSource.spec.ts b/packages/data-context/test/unit/sources/VersionsDataSource.spec.ts index 31ae8f2283b5..3bb8cdfc8cbe 100644 --- a/packages/data-context/test/unit/sources/VersionsDataSource.spec.ts +++ b/packages/data-context/test/unit/sources/VersionsDataSource.spec.ts @@ -14,7 +14,7 @@ describe('VersionsDataSource', () => { context('.versions', () => { let ctx: DataContext let fetchStub: sinon.SinonStub - let isDependencyInstalledStub: sinon.SinonStub + let isDependencyInstalledByNameStub: sinon.SinonStub let mockNow: Date = new Date() let versionsDataSource: VersionsDataSource let currentCypressVersion: string = pkg.version @@ -36,12 +36,12 @@ describe('VersionsDataSource', () => { ctx.coreData.currentTestingType = 'e2e' fetchStub = sinon.stub() - isDependencyInstalledStub = sinon.stub() + isDependencyInstalledByNameStub = sinon.stub() }) beforeEach(() => { sinon.stub(ctx.util, 'fetch').callsFake(fetchStub) - sinon.stub(ctx.util, 'isDependencyInstalled').callsFake(isDependencyInstalledStub) + sinon.stub(ctx.util, 'isDependencyInstalledByName').callsFake(isDependencyInstalledByNameStub) sinon.stub(os, 'platform').returns('darwin') sinon.stub(os, 'arch').returns('x64') sinon.useFakeTimers({ now: mockNow }) @@ -194,31 +194,41 @@ describe('VersionsDataSource', () => { }) it('generates x-framework, x-bundler, and x-dependencies headers', async () => { - isDependencyInstalledStub.callsFake(async (dependency) => { + isDependencyInstalledByNameStub.callsFake(async (packageName) => { // Should include any resolved dependency with a valid version - if (dependency.package === 'react') { + if (packageName === 'react') { return { - dependency, + dependency: packageName, detectedVersion: '1.2.3', - satisfied: true, } as Cypress.DependencyToInstall } - // Not satisfied dependency should be excluded - if (dependency.package === 'vue') { + if (packageName === 'vue') { return { - dependency, + dependency: packageName, detectedVersion: '4.5.6', - satisfied: false, } } - // Satisfied dependency without resolved version should result in -1 - if (dependency.package === 'typescript') { + if (packageName === '@builder.io/qwik') { return { - dependency, + dependency: packageName, + detectedVersion: '1.1.4', + } + } + + if (packageName === '@playwright/experimental-ct-core') { + return { + dependency: packageName, + detectedVersion: '1.33.0', + } + } + + // Dependency without resolved version should be excluded + if (packageName === 'typescript') { + return { + dependency: packageName, detectedVersion: null, - satisfied: true, } } @@ -238,7 +248,7 @@ describe('VersionsDataSource', () => { headers: sinon.match({ 'x-framework': 'react', 'x-dev-server': 'vite', - 'x-dependencies': 'typescript@-1,react@1', + 'x-dependencies': 'react@1.2.3,vue@4.5.6,@builder.io/qwik@1.1.4,@playwright/experimental-ct-core@1.33.0', }), }, ) diff --git a/packages/launchpad/cypress/e2e/open-mode.cy.ts b/packages/launchpad/cypress/e2e/open-mode.cy.ts index 458e13d71641..68dea29ade1e 100644 --- a/packages/launchpad/cypress/e2e/open-mode.cy.ts +++ b/packages/launchpad/cypress/e2e/open-mode.cy.ts @@ -65,7 +65,30 @@ describe('Launchpad: Open Mode', () => { cy.openProject('todos', ['--e2e']) }) - it('includes `x-framework`, `x-dev-server`, and `x-dependencies` headers, even when launched in e2e mode', () => { + it('includes `x-framework`, `x-dev-server`, and `x-dependencies` headers, even when launched in e2e mode if this is the initial launch of Cypress', () => { + cy.withCtx((ctx) => { + ctx.versions['_initialLaunch'] = true + }) + + cy.visitLaunchpad() + cy.skipWelcome() + cy.get('h1').should('contain', 'Choose a browser') + cy.withCtx((ctx, o) => { + expect(ctx.util.fetch).to.have.been.calledWithMatch('https://download.cypress.io/desktop.json', { + headers: { + 'x-framework': 'react', + 'x-dev-server': 'webpack', + 'x-dependencies': 'typescript@4.7.4', + }, + }) + }) + }) + + it('does not include `x-dependencies` header, if this is not the initial launch of Cypress', () => { + cy.withCtx((ctx) => { + ctx.versions['_initialLaunch'] = false + }) + cy.visitLaunchpad() cy.skipWelcome() cy.get('h1').should('contain', 'Choose a browser') @@ -74,7 +97,6 @@ describe('Launchpad: Open Mode', () => { headers: { 'x-framework': 'react', 'x-dev-server': 'webpack', - 'x-dependencies': 'typescript@4', }, }) }) diff --git a/packages/scaffold-config/src/dependencies.ts b/packages/scaffold-config/src/dependencies.ts index 27ebd58d590b..54b5c9d7d48e 100644 --- a/packages/scaffold-config/src/dependencies.ts +++ b/packages/scaffold-config/src/dependencies.ts @@ -175,3 +175,78 @@ export const WIZARD_BUNDLERS = [ WIZARD_DEPENDENCY_WEBPACK, WIZARD_DEPENDENCY_VITE, ] as const + +const componentDependenciesOfInterest = [ + '@angular/cli', + '@angular-devkit/build-angular', + '@angular/core', + '@angular/common', + '@angular/platform-browser-dynamic', + 'react', + 'react-dom', + 'react-scripts', + 'vue', + '@vue/cli-service', + 'svelte', + 'solid-js', + 'lit', + 'preact', + 'preact-cli', + 'ember', + '@stencil/core', + '@builder.io/qwik', + 'alpinejs', + '@glimmer/component', + 'typescript', +] + +const bundlerDependenciesOfInterest = [ + 'vite', + 'webpack', + 'parcel', + 'rollup', + 'snowpack', +] + +const testingDependenciesOfInterest = [ + 'jest', + 'jsdom', + 'jest-preview', + 'storybook', + '@storybook/addon-interactions', + '@storybook/addon-a11y', + 'chromatic', + '@testing-library/react', + '@testing-library/react-hooks', + '@testing-library/dom', + '@testing-library/jest-dom', + '@testing-library/cypress', + '@testing-library/user-event', + '@testing-library/vue', + '@testing-library/svelte', + '@testing-library/preact', + 'happy-dom', + 'vitest', + 'vitest-preview', + 'selenium-webdriver', + 'nightwatch', + 'karma', + 'playwright', + 'playwright-core', + '@playwright/experimental-ct-core', + '@playwright/experimental-ct-react', + '@playwright/experimental-ct-svelte', + '@playwright/experimental-ct-vue', + '@playwright/experimental-ct-vue2', + '@playwright/experimental-ct-solid', + '@playwright/experimental-ct-react17', + 'axe-core', + 'jest-axe', + 'enzyme', +] + +export const dependencyNamesToDetect = [ + ...componentDependenciesOfInterest, + ...bundlerDependenciesOfInterest, + ...testingDependenciesOfInterest, +] diff --git a/packages/scaffold-config/src/frameworks.ts b/packages/scaffold-config/src/frameworks.ts index 8553d2553767..caaad49ac886 100644 --- a/packages/scaffold-config/src/frameworks.ts +++ b/packages/scaffold-config/src/frameworks.ts @@ -14,6 +14,37 @@ export type WizardBundler = typeof dependencies.WIZARD_BUNDLERS[number] export type CodeGenFramework = Cypress.ResolvedComponentFrameworkDefinition['codeGenFramework'] +export async function isDependencyInstalledByName (packageName: string, projectPath: string): Promise<{dependency: string, detectedVersion: string | null}> { + let detectedVersion: string | null = null + + try { + debug('detecting %s in %s', packageName, projectPath) + + const packageFilePath = resolvePackagePath(packageName, projectPath, false) + + if (!packageFilePath) { + throw new Error('unable to resolve package file') + } + + const pkg = await fs.readJson(packageFilePath) as PkgJson + + debug('found package.json %o', pkg) + + if (!pkg.version) { + throw Error(`${pkg.version} for ${packageName} is not a valid semantic version.`) + } + + detectedVersion = pkg.version + } catch (e) { + debug('error when detecting %s: %s', packageName, e.message) + } + + return { + dependency: packageName, + detectedVersion, + } +} + export async function isDependencyInstalled (dependency: Cypress.CypressComponentDependency, projectPath: string): Promise { try { debug('detecting %s in %s', dependency.package, projectPath) diff --git a/packages/scaffold-config/test/unit/frameworks.spec.ts b/packages/scaffold-config/test/unit/frameworks.spec.ts new file mode 100644 index 000000000000..1634b29f23db --- /dev/null +++ b/packages/scaffold-config/test/unit/frameworks.spec.ts @@ -0,0 +1,58 @@ +import { expect } from 'chai' +import fs from 'fs-extra' +import path from 'path' +import os from 'os' +import { isDependencyInstalledByName } from '../../src/frameworks' + +describe('frameworks', () => { + describe('isDependencyInstalledByName', () => { + const TEMP_DIR = path.join(os.tmpdir(), 'is-dependency-installed-by-name-tmp') + const TEMP_NODE_MODULES = path.join(TEMP_DIR, 'node_modules') + + beforeEach(async () => { + await fs.mkdir(TEMP_DIR) + await fs.mkdir(TEMP_NODE_MODULES) + }) + + afterEach(async () => { + await fs.rm(TEMP_DIR, { recursive: true }) + }) + + it('returns null version if dependency is not found', async () => { + const result = await isDependencyInstalledByName('my-dep', TEMP_DIR) + + expect(result).to.eql({ dependency: 'my-dep', detectedVersion: null }) + }) + + it('returns null version if there is no version in the package file', async () => { + await fs.mkdir(path.join(TEMP_NODE_MODULES, 'my-dep')) + await fs.writeFile(path.join(TEMP_NODE_MODULES, 'my-dep', 'package.json'), `{ + "name": "my-dep", + "private": false, + "main": "index.js", + "license": "MIT" + } + `) + + const result = await isDependencyInstalledByName('my-dep', TEMP_DIR) + + expect(result).to.eql({ dependency: 'my-dep', detectedVersion: null }) + }) + + it('returns package version if it finds the dependency', async () => { + await fs.mkdir(path.join(TEMP_NODE_MODULES, 'my-dep')) + await fs.writeFile(path.join(TEMP_NODE_MODULES, 'my-dep', 'package.json'), `{ + "name": "my-dep", + "private": false, + "version": "1.2.3", + "main": "index.js", + "license": "MIT" + } + `) + + const result = await isDependencyInstalledByName('my-dep', TEMP_DIR) + + expect(result).to.eql({ dependency: 'my-dep', detectedVersion: '1.2.3' }) + }) + }) +}) From 80985c8d4b29884e086b1608be5205781e81432d Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Mon, 22 May 2023 15:58:06 -0500 Subject: [PATCH 02/13] chore: update changlelog script to handle revert pr ref (#26801) --- .../semantic-commits/get-binary-release-data.js | 16 ++++++++++++---- .../validate-binary-changelog.js | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/semantic-commits/get-binary-release-data.js b/scripts/semantic-commits/get-binary-release-data.js index 71787718baa5..f3febdffa570 100644 --- a/scripts/semantic-commits/get-binary-release-data.js +++ b/scripts/semantic-commits/get-binary-release-data.js @@ -66,7 +66,15 @@ const getReleaseData = async (latestReleaseInfo) => { const { type: semanticType, references } = semanticResult - if (!references.length || !references[0].issue) { + if (!references.length) { + console.log('Commit does not have an associated pull request number...') + + return + } + + const ref = references.find((r) => !r.raw.includes('revert #')) + + if (!ref) { console.log('Commit does not have an associated pull request number...') return @@ -75,7 +83,7 @@ const getReleaseData = async (latestReleaseInfo) => { const { data: pullRequest } = await octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', { owner: 'cypress-io', repo: 'cypress', - pull_number: references[0].issue, + pull_number: ref.issue, }) const associatedIssues = getLinkedIssues(pullRequest.body) @@ -83,11 +91,11 @@ const getReleaseData = async (latestReleaseInfo) => { commits.push({ commitMessage: semanticResult.header, semanticType, - prNumber: references[0].issue, + prNumber: ref.issue, associatedIssues, }) - prsInRelease.push(`https://github.com/cypress-io/cypress/pull/${references[0].issue}`) + prsInRelease.push(`https://github.com/cypress-io/cypress/pull/${ref.issue}`) associatedIssues.forEach((issueNumber) => { issuesInRelease.push(`https://github.com/cypress-io/cypress/issues/${issueNumber}`) diff --git a/scripts/semantic-commits/validate-binary-changelog.js b/scripts/semantic-commits/validate-binary-changelog.js index 094d8df20a50..9d0ac025377b 100644 --- a/scripts/semantic-commits/validate-binary-changelog.js +++ b/scripts/semantic-commits/validate-binary-changelog.js @@ -12,7 +12,7 @@ const changelog = async () => { if (process.env.CIRCLECI) { console.log({ checkedInBinaryVersion }) - if (process.env.CIRCLE_BRANCH !== 'develop' && process.env.CIRCLE_BRANCH !== 'release-12.5.0' && !/^release\/\d+\.\d+\.\d+$/.test(process.env.CIRCLE_BRANCH) && !hasVersionBump) { + if (process.env.CIRCLE_BRANCH !== 'develop' && process.env.CIRCLE_BRANCH !== 'fix-changelog-script' && !/^release\/\d+\.\d+\.\d+$/.test(process.env.CIRCLE_BRANCH) && !hasVersionBump) { console.log('Only verify the entire changelog for develop, a release branch or any branch that bumped to the Cypress version in the package.json.') return From e821f854625bd1d3c5c439eff2c4303d098eb8a3 Mon Sep 17 00:00:00 2001 From: Dave Kasper Date: Mon, 22 May 2023 16:09:35 -0500 Subject: [PATCH 03/13] fix: Correct typescript scaffold dependency (#26815) * fix: correct typescript scaffold dependency (#26204) * add changelog * Update change log for PR comment Co-authored-by: Mike Plummer --------- Co-authored-by: Mike Plummer Co-authored-by: Mark Noonan --- cli/CHANGELOG.md | 1 + packages/scaffold-config/src/dependencies.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 610120d7cec0..90e03ee4f325 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -13,6 +13,7 @@ _Released 05/23/2023 (PENDING)_ - Moved `types` condition to the front of `package.json#exports` since keys there are meant to be order-sensitive. Fixed in [#26630](https://github.com/cypress-io/cypress/pull/26630). - Fixed an issue where newly-installed dependencies would not be detected during Component Testing setup. Addresses [#26685](https://github.com/cypress-io/cypress/issues/26685). - Fixed a UI regression that was flashing an "empty" state inappropriately when loading the Debug page. Fixed in [#26761](https://github.com/cypress-io/cypress/pull/26761). +- Fixed an issue in Component Testing setup where TypeScript version 5 was not properly detected. Fixes [#26204](https://github.com/cypress-io/cypress/issues/26204). **Misc:** diff --git a/packages/scaffold-config/src/dependencies.ts b/packages/scaffold-config/src/dependencies.ts index 54b5c9d7d48e..7db54e8c7113 100644 --- a/packages/scaffold-config/src/dependencies.ts +++ b/packages/scaffold-config/src/dependencies.ts @@ -49,7 +49,7 @@ export const WIZARD_DEPENDENCY_TYPESCRIPT = { package: 'typescript', installer: 'typescript', description: 'TypeScript is a language for application-scale JavaScript', - minVersion: '^=3.4.0 || ^=4.0.0' || '^=5.0.0', + minVersion: '^=3.4.0 || ^=4.0.0 || ^=5.0.0', } as const export const WIZARD_DEPENDENCY_REACT_SCRIPTS = { From 32796de014e5d7b11d67bd1b470becef99a73af2 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Tue, 23 May 2023 10:24:50 -0400 Subject: [PATCH 04/13] chore: 12.13.0 prep (#26833) --- cli/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 90e03ee4f325..82c43082f20d 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,11 +1,11 @@ -## 12.12.1 +## 12.13.0 -_Released 05/23/2023 (PENDING)_ +_Released 05/23/2023_ **Features:** -- Add Git-related messages for the [Runs page](https://docs.cypress.io/guides/core-concepts/cypress-app#Runs) and [Debug page](https://docs.cypress.io/guides/cloud/runs#Debug) when users aren't using Git or there are no recorded runs for the current branch. Fixes [#26680](https://github.com/cypress-io/cypress/issues/26680). +- Adds Git-related messages for the [Runs page](https://docs.cypress.io/guides/core-concepts/cypress-app#Runs) and [Debug page](https://docs.cypress.io/guides/cloud/runs#Debug) when users aren't using Git or there are no recorded runs for the current branch. Fixes [#26680](https://github.com/cypress-io/cypress/issues/26680). **Bugfixes:** From b71788d87df83eeb844c7e9de2ee106d2724fa9c Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Tue, 23 May 2023 15:18:49 -0400 Subject: [PATCH 05/13] chore: 12.13.0 release (#26834) * chore: 12.13.0 changelog fix * remove pending, bump version * fix typo --- cli/CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 82c43082f20d..78a319df2f8c 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -9,7 +9,7 @@ _Released 05/23/2023_ **Bugfixes:** -- Reverted [#26452](https://github.com/cypress-io/cypress/pull/26630) which introduced a bug that prevents users from using End to End with Yarn 3. Fixed in [#26735](https://github.com/cypress-io/cypress/pull/26735). Fixes [#26676](https://github.com/cypress-io/cypress/issues/26676). +- Reverted [#26452](https://github.com/cypress-io/cypress/pull/26452) which introduced a bug that prevents users from using End to End with Yarn 3. Fixed in [#26735](https://github.com/cypress-io/cypress/pull/26735). Fixes [#26676](https://github.com/cypress-io/cypress/issues/26676). - Moved `types` condition to the front of `package.json#exports` since keys there are meant to be order-sensitive. Fixed in [#26630](https://github.com/cypress-io/cypress/pull/26630). - Fixed an issue where newly-installed dependencies would not be detected during Component Testing setup. Addresses [#26685](https://github.com/cypress-io/cypress/issues/26685). - Fixed a UI regression that was flashing an "empty" state inappropriately when loading the Debug page. Fixed in [#26761](https://github.com/cypress-io/cypress/pull/26761). diff --git a/package.json b/package.json index 60ef7bb4bfcc..4a0a0ab691d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cypress", - "version": "12.12.0", + "version": "12.13.0", "description": "Cypress is a next generation front end testing tool built for the modern web", "private": true, "scripts": { From 3df65f73e134571f315534d4da304df68ffbb114 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 May 2023 15:49:10 -0400 Subject: [PATCH 06/13] chore: release @cypress/vite-plugin-cypress-esm-v1.0.1 [skip ci] --- npm/vite-plugin-cypress-esm/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/npm/vite-plugin-cypress-esm/CHANGELOG.md b/npm/vite-plugin-cypress-esm/CHANGELOG.md index b27bdd0a39f4..bb4139b30369 100644 --- a/npm/vite-plugin-cypress-esm/CHANGELOG.md +++ b/npm/vite-plugin-cypress-esm/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@cypress/vite-plugin-cypress-esm-v1.0.1](https://github.com/cypress-io/cypress/compare/@cypress/vite-plugin-cypress-esm-v1.0.0...@cypress/vite-plugin-cypress-esm-v1.0.1) (2023-05-23) + + +### Bug Fixes + +* Vite esm plugin issues ([#26714](https://github.com/cypress-io/cypress/issues/26714)) ([38b9526](https://github.com/cypress-io/cypress/commit/38b952622e034928df0d6e5d571e83c22d98ce86)) + # @cypress/vite-plugin-cypress-esm-v1.0.0 (2023-05-04) From 58b9ccc0a8dedcf09afb889aac97f59b73ccd97a Mon Sep 17 00:00:00 2001 From: Mike Plummer Date: Wed, 24 May 2023 07:32:49 -0500 Subject: [PATCH 07/13] chore: Implement runSpec mutation (#26782) --- .../app/cypress/e2e/cypress-in-cypress.cy.ts | 33 ++++ packages/app/src/runner/event-manager.ts | 9 +- packages/app/src/runner/index.ts | 4 + packages/config/src/browser.ts | 2 + packages/config/src/options.ts | 7 +- .../src/actions/ProjectActions.ts | 172 ++++++++++++++++++ .../src/data/ProjectLifecycleManager.ts | 2 +- .../src/sources/ProjectDataSource.ts | 53 +++++- .../test/unit/actions/ProjectActions.spec.ts | 161 ++++++++++++++++ packages/data-context/test/unit/helper.ts | 1 + .../unit/sources/ProjectDataSource.spec.ts | 59 +++++- packages/graphql/schemas/schema.graphql | 43 +++++ .../enumTypes/gql-RunSpecErrorCodeEnum.ts | 8 + .../src/schemaTypes/enumTypes/index.ts | 1 + .../schemaTypes/objectTypes/gql-Mutation.ts | 20 +- .../objectTypes/gql-RunSpecError.ts | 16 ++ .../objectTypes/gql-RunSpecResponse.ts | 21 +++ .../src/schemaTypes/objectTypes/index.ts | 2 + .../schemaTypes/unions/gql-RunSpecResult.ts | 14 ++ .../graphql/src/schemaTypes/unions/index.ts | 1 + packages/server/lib/makeDataContext.ts | 3 + packages/server/lib/open_project.ts | 2 + 22 files changed, 614 insertions(+), 20 deletions(-) create mode 100644 packages/graphql/src/schemaTypes/enumTypes/gql-RunSpecErrorCodeEnum.ts create mode 100644 packages/graphql/src/schemaTypes/objectTypes/gql-RunSpecError.ts create mode 100644 packages/graphql/src/schemaTypes/objectTypes/gql-RunSpecResponse.ts create mode 100644 packages/graphql/src/schemaTypes/unions/gql-RunSpecResult.ts diff --git a/packages/app/cypress/e2e/cypress-in-cypress.cy.ts b/packages/app/cypress/e2e/cypress-in-cypress.cy.ts index d49d3bf8aaba..5e553e0e9ddd 100644 --- a/packages/app/cypress/e2e/cypress-in-cypress.cy.ts +++ b/packages/app/cypress/e2e/cypress-in-cypress.cy.ts @@ -395,4 +395,37 @@ describe('Cypress in Cypress', { viewportWidth: 1500, defaultCommandTimeout: 100 expect(ctx.actions.project.initializeActiveProject).to.be.called }) }) + + describe('runSpec mutation', () => { + it('should trigger expected spec from POST', () => { + startAtSpecsPage('e2e') + + cy.contains('E2E specs').should('be.visible') + + cy.withCtx(async (ctx) => { + const url = `http://127.0.0.1:${ctx.gqlServerPort}/__launchpad/graphql?` + const payload = `{"query":"mutation{\\nrunSpec(specPath:\\"cypress/e2e/dom-content.spec.js\\"){\\n__typename\\n... on RunSpecResponse{\\ntestingType\\nbrowser{\\nid\\nname\\n}\\nspec{\\nid\\nname\\n}\\n}\\n}\\n}","variables":null}` + + ctx.coreData.app.browserStatus = 'open' + + /* + Note: If this test starts failing, this fetch is the likely culprit. + Validate the GQL payload above is still valid by logging the fetch response JSON + */ + + await ctx.util.fetch( + url, + { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: payload, + }, + ) + }) + + cy.contains('Dom Content').should('be.visible') + }) + }) }) diff --git a/packages/app/src/runner/event-manager.ts b/packages/app/src/runner/event-manager.ts index ab929b03b5d3..e8cb2180f81b 100644 --- a/packages/app/src/runner/event-manager.ts +++ b/packages/app/src/runner/event-manager.ts @@ -127,10 +127,6 @@ export class EventManager { runnerUiStore.setAutomationStatus(connected) }) - this.ws.on('change:to:url', (url) => { - window.location.href = url - }) - this.ws.on('update:telemetry:context', (contextString) => { const context = JSON.parse(contextString) @@ -815,7 +811,12 @@ export class EventManager { stop () { this.localBus.removeAllListeners() + + // Grab existing listeners for url change event, we want to preserve them + const urlChangeListeners = this.ws.listeners('change:to:url') + this.ws.off() + urlChangeListeners.forEach((listener) => this.ws.on('change:to:url', listener)) } async teardown (state: MobxRunnerStore, isRerun = false) { diff --git a/packages/app/src/runner/index.ts b/packages/app/src/runner/index.ts index 4e1dfe0053d7..69dd26557db8 100644 --- a/packages/app/src/runner/index.ts +++ b/packages/app/src/runner/index.ts @@ -37,6 +37,10 @@ export function createWebsocket (config: Cypress.Config) { ws.emit('runner:connected') }) + ws.on('change:to:url', (url) => { + window.location.href = url + }) + return ws } diff --git a/packages/config/src/browser.ts b/packages/config/src/browser.ts index 509886eb5dee..94d21f2bf7e6 100644 --- a/packages/config/src/browser.ts +++ b/packages/config/src/browser.ts @@ -2,6 +2,7 @@ import _ from 'lodash' import Debug from 'debug' import { defaultSpecPattern, + defaultExcludeSpecPattern, options, breakingOptions, breakingRootOptions, @@ -17,6 +18,7 @@ import * as validation from './validation' export { defaultSpecPattern, + defaultExcludeSpecPattern, options, breakingOptions, BreakingOption, diff --git a/packages/config/src/options.ts b/packages/config/src/options.ts index 2a48153160ba..d32010375a55 100644 --- a/packages/config/src/options.ts +++ b/packages/config/src/options.ts @@ -120,6 +120,11 @@ export const defaultSpecPattern = { component: '**/*.cy.{js,jsx,ts,tsx}', } +export const defaultExcludeSpecPattern = { + e2e: '*.hot-update.js', + component: ['**/__snapshots__/*', '**/__image_snapshots__/*'], +} + // NOTE: // If you add/remove/change a config value, make sure to update the following // - cli/types/index.d.ts (including allowed config options on TestOptions) @@ -271,7 +276,7 @@ const driverConfigOptions: Array = [ requireRestartOnChange: 'server', }, { name: 'excludeSpecPattern', - defaultValue: (options: Record = {}) => options.testingType === 'component' ? ['**/__snapshots__/*', '**/__image_snapshots__/*'] : '*.hot-update.js', + defaultValue: (options: Record = {}) => options.testingType === 'component' ? defaultExcludeSpecPattern.component : defaultExcludeSpecPattern.e2e, validation: validate.isStringOrArrayOfStrings, overrideLevel: 'any', }, { diff --git a/packages/data-context/src/actions/ProjectActions.ts b/packages/data-context/src/actions/ProjectActions.ts index e0f52325c0b1..d5d28934b2e9 100644 --- a/packages/data-context/src/actions/ProjectActions.ts +++ b/packages/data-context/src/actions/ProjectActions.ts @@ -12,6 +12,14 @@ import templates from '../codegen/templates' import { insertValuesInConfigFile } from '../util' import { getError } from '@packages/errors' import { resetIssuedWarnings } from '@packages/config' +import type { RunSpecErrorCode } from '@packages/graphql/src/schemaTypes' +import debugLib from 'debug' + +export class RunSpecError extends Error { + constructor (public code: typeof RunSpecErrorCode[number], msg: string) { + super(msg) + } +} export interface ProjectApiShape { /** @@ -46,6 +54,7 @@ export interface ProjectApiShape { isListening: (url: string) => Promise resetBrowserTabsForNextTest(shouldKeepTabOpen: boolean): Promise resetServer(): void + runSpec(spec: Cypress.Spec): Promise } export interface FindSpecs { @@ -76,6 +85,8 @@ type SetForceReconfigureProjectByTestingType = { testingType?: TestingType } +const debug = debugLib('cypress:data-context:ProjectActions') + export class ProjectActions { constructor (private ctx: DataContext) {} @@ -462,4 +473,165 @@ export class ProjectActions { await this.ctx.actions.wizard.scaffoldTestingType() } } + + async runSpec ({ specPath }: { specPath: string}) { + const waitForBrowserToOpen = async () => { + const browserStatusSubscription = this.ctx.emitter.subscribeTo('browserStatusChange', { sendInitial: false }) + + // Wait for browser to finish launching. Browser is either launched from scratch + // or relaunched when switching testing types - we need to wait in either case + // We wait a maximum of 3 seconds so we don't block indefinitely in case something + // goes sideways with the browser launch process. This is broken up into three + // separate 'waits' in case we have to watch a browser relaunch (close > opening > open) + debug('Waiting for browser to report `open`') + let maxIterations = 3 + + while (this.ctx.coreData.app.browserStatus !== 'open') { + await Promise.race([ + new Promise((resolve) => setTimeout(resolve, 1000)), + browserStatusSubscription.next(), + ]) + + if (--maxIterations === 0) { + break + } + } + + await browserStatusSubscription.return(undefined as any) + } + + try { + if (!this.ctx.currentProject) { + throw new RunSpecError('NO_PROJECT', 'A project must be open prior to attempting to run a spec') + } + + if (!specPath) { + throw new RunSpecError('NO_SPEC_PATH', '`specPath` must be a non-empty string') + } + + let targetTestingType: TestingType + + // Check to see whether input specPath matches the specPattern for one or the other testing type + // If it matches neither then we can't run the spec and we should error + if (await this.ctx.project.matchesSpecPattern(specPath, 'e2e')) { + targetTestingType = 'e2e' + } else if (await this.ctx.project.matchesSpecPattern(specPath, 'component')) { + targetTestingType = 'component' + } else { + throw new RunSpecError('NO_SPEC_PATTERN_MATCH', 'Unable to determine testing type, spec does not match any configured specPattern') + } + + debug(`Spec %s matches '${targetTestingType}' pattern`, specPath) + + const absoluteSpecPath = this.ctx.path.resolve(this.ctx.currentProject, specPath) + + debug('Attempting to launch spec %s', absoluteSpecPath) + + // Look to see if there's actually a file at the target location + // This helps us avoid switching testingType *then* finding out the spec doesn't exist + if (!this.ctx.fs.existsSync(absoluteSpecPath)) { + throw new RunSpecError('SPEC_NOT_FOUND', `No file exists at path ${absoluteSpecPath}`) + } + + // We now know what testingType we need to be in - if we're already there, great + // If not, verify that type is configured then switch (or throw an error if not configured) + if (this.ctx.coreData.currentTestingType !== targetTestingType) { + if (!this.ctx.lifecycleManager.isTestingTypeConfigured(targetTestingType)) { + throw new RunSpecError('TESTING_TYPE_NOT_CONFIGURED', `Input path matched specPattern for '${targetTestingType}' testing type, but it is not configured.`) + } + + debug('Setting testing type to %s', targetTestingType) + + const specChangeSubscription = this.ctx.emitter.subscribeTo('specsChange', { sendInitial: false }) + + const originalTestingType = this.ctx.coreData.currentTestingType + + // Temporarily toggle testing type so the `activeBrowser` can be initialized + // for the targeted testing type. Browser has to be initialized prior to our "relaunch" + // call below - this can be an issue when Cypress is still on the launchpad and no + // browser has been launched yet + this.ctx.lifecycleManager.setCurrentTestingType(targetTestingType) + await this.ctx.lifecycleManager.setInitialActiveBrowser() + this.ctx.lifecycleManager.setCurrentTestingType(originalTestingType) + + // This is the magic sauce - we now have a browser selected, so this will toggle + // the testing type, trigger specs to update, and launch the browser + await this.switchTestingTypesAndRelaunch(targetTestingType) + + await waitForBrowserToOpen() + + // When testing type changes we need to wait for the specWatcher to trigger and load new specs + // otherwise our call to `getCurrentSpecByAbsolute` below will fail + // Wait a maximum of 2 seconds just in case something breaks with the event subscription + // so we don't block indefinitely + debug('Waiting for specs to finish loading') + await Promise.race([ + new Promise((resolve) => setTimeout(resolve, 2000)), + specChangeSubscription.next(), + ]) + + // Close out subscription + await specChangeSubscription.return(undefined) + } else { + debug('Already in %s testing mode', targetTestingType) + } + + // This accounts for an edge case where a testing type has been previously opened, but + // the user then backs out to the testing type selector in launchpad. In that scenario, + // the testingType switch logic above does not trigger the browser to open, so we do it + // manually here + if (this.ctx.coreData.app.browserStatus === 'closed') { + debug('No browser instance, launching...') + await this.ctx.lifecycleManager.setInitialActiveBrowser() + + await this.api.launchProject( + this.ctx.coreData.activeBrowser!, + { + name: '', + absolute: '', + relative: '', + specType: targetTestingType === 'e2e' ? 'integration' : 'component', + }, + ) + + debug('Browser launched') + } else { + debug(`Browser already running, status ${this.ctx.coreData.app.browserStatus}`) + + if (this.ctx.coreData.app.browserStatus !== 'open') { + await waitForBrowserToOpen() + } + } + + // Now that we're in the correct testingType, verify the requested spec actually exists + // We don't have specs available until a testingType is loaded, so even through we validated + // a matching file exists above it may not end up loading as a valid spec so we validate that here + const spec = this.ctx.project.getCurrentSpecByAbsolute(absoluteSpecPath) + + if (!spec) { + throw new RunSpecError('SPEC_NOT_FOUND', `Unable to find matching spec with path ${absoluteSpecPath}`) + } + + const browser = this.ctx.coreData.activeBrowser! + + // Hooray, everything looks good and we're all set up + // Try to launch the requested spec by navigating to it in the browser + await this.api.runSpec(spec) + + return { + testingType: targetTestingType, + browser, + spec, + } + } catch (err) { + if (!(err instanceof RunSpecError)) { + debug('Unexpected error during `runSpec` %o', err) + } + + return { + code: err instanceof RunSpecError ? err.code : 'GENERAL_ERROR', + detailMessage: err.message, + } + } + } } diff --git a/packages/data-context/src/data/ProjectLifecycleManager.ts b/packages/data-context/src/data/ProjectLifecycleManager.ts index 2c1c4a6a9ad7..afac43214656 100644 --- a/packages/data-context/src/data/ProjectLifecycleManager.ts +++ b/packages/data-context/src/data/ProjectLifecycleManager.ts @@ -584,7 +584,7 @@ export class ProjectLifecycleManager { * this sources the config from the various config sources */ async getFullInitialConfig (options: Partial = this.ctx.modeOptions, withBrowsers = true): Promise { - assert(this._configManager, 'Cannot get full config a config manager') + assert(this._configManager, 'Cannot get full config without a config manager') return this._configManager.getFullInitialConfig(options, withBrowsers) } diff --git a/packages/data-context/src/sources/ProjectDataSource.ts b/packages/data-context/src/sources/ProjectDataSource.ts index b8cea8e9fa63..95284e536cb8 100644 --- a/packages/data-context/src/sources/ProjectDataSource.ts +++ b/packages/data-context/src/sources/ProjectDataSource.ts @@ -7,7 +7,7 @@ import path from 'path' import Debug from 'debug' import commonPathPrefix from 'common-path-prefix' import type { FSWatcher } from 'chokidar' -import { defaultSpecPattern } from '@packages/config' +import { defaultSpecPattern, defaultExcludeSpecPattern } from '@packages/config' import parseGlob from 'parse-glob' import micromatch from 'micromatch' import RandExp from 'randexp' @@ -23,12 +23,20 @@ import type { ProjectShape } from '../data' import type { FindSpecs } from '../actions' import { FileExtension, getDefaultSpecFileName } from './migration/utils' +type SpecPatterns = { + specPattern?: string[] + excludeSpecPattern?: string[] +} + interface MatchedSpecs { projectRoot: string testingType: Cypress.TestingType specAbsolutePaths: string[] specPattern: string | string[] } + +const toArray = (val?: string | string[]) => val ? typeof val === 'string' ? [val] : val : undefined + export function matchedSpecs ({ projectRoot, testingType, @@ -258,12 +266,10 @@ export class ProjectDataSource { this.ctx.coreData.app.relaunchBrowser = relaunchBrowser } - async specPatterns (): Promise<{ - specPattern?: string[] - excludeSpecPattern?: string[] - }> { - const toArray = (val?: string | string[]) => val ? typeof val === 'string' ? [val] : val : undefined - + /** + * Retrieve the applicable spec patterns for the current testing type + */ + async specPatterns (): Promise { const config = await this.getConfig() return { @@ -272,6 +278,26 @@ export class ProjectDataSource { } } + /** + * Retrieve the applicable spec patterns for a given testing type. Can be used to check whether + * a spec satisfies the pattern when outside a given testing type. + */ + async specPatternsByTestingType (testingType: TestingType): Promise { + const configFile = await this.ctx.lifecycleManager.getConfigFileContents() + + if (testingType === 'e2e') { + return { + specPattern: toArray(configFile.e2e?.specPattern ?? defaultSpecPattern.e2e), + excludeSpecPattern: toArray(configFile.e2e?.excludeSpecPattern ?? defaultExcludeSpecPattern.e2e), + } + } + + return { + specPattern: toArray(configFile.component?.specPattern ?? defaultSpecPattern.component), + excludeSpecPattern: toArray(configFile.component?.excludeSpecPattern ?? defaultExcludeSpecPattern.component), + } + } + async findSpecs ({ projectRoot, testingType, @@ -460,14 +486,21 @@ export class ProjectDataSource { }) } - async matchesSpecPattern (specFile: string): Promise { - if (!this.ctx.currentProject || !this.ctx.coreData.currentTestingType) { + /** + * Determines whether a given spec file satisfies the spec pattern *and* does not satisfy any + * exclusionary pattern. By default it will check the spec pattern for the currently-active + * testing type, but a target testing type can be supplied via optional parameter. + */ + async matchesSpecPattern (specFile: string, testingType?: TestingType): Promise { + const targetTestingType = testingType || this.ctx.coreData.currentTestingType + + if (!this.ctx.currentProject || !targetTestingType) { return false } const MINIMATCH_OPTIONS = { dot: true, matchBase: true } - const { specPattern = [], excludeSpecPattern = [] } = await this.ctx.project.specPatterns() + const { specPattern = [], excludeSpecPattern = [] } = await this.ctx.project.specPatternsByTestingType(targetTestingType) for (const pattern of excludeSpecPattern) { if (minimatch(specFile, pattern, MINIMATCH_OPTIONS)) { diff --git a/packages/data-context/test/unit/actions/ProjectActions.spec.ts b/packages/data-context/test/unit/actions/ProjectActions.spec.ts index dc95bf2175a1..b814efe85adc 100644 --- a/packages/data-context/test/unit/actions/ProjectActions.spec.ts +++ b/packages/data-context/test/unit/actions/ProjectActions.spec.ts @@ -103,4 +103,165 @@ describe('ProjectActions', () => { }) }) }) + + describe('runSpec', () => { + context('no project', () => { + it('should fail with `NO_PROJECT`', async () => { + const result = await ctx.actions.project.runSpec({ specPath: 'e2e/abc.cy.ts' }) + + sinon.assert.match(result, { + code: 'NO_PROJECT', + detailMessage: sinon.match.string, + }) + }) + }) + + context('empty specPath', () => { + beforeEach(() => { + ctx.coreData.currentProject = '/cy-project' + }) + + it('should fail with `NO_SPEC_PATH`', async () => { + const result = await ctx.actions.project.runSpec({ specPath: '' }) + + sinon.assert.match(result, { + code: 'NO_SPEC_PATH', + detailMessage: sinon.match.string, + }) + }) + }) + + context('no specPattern match', () => { + beforeEach(() => { + ctx.coreData.currentProject = '/cy-project' + sinon.stub(ctx.project, 'matchesSpecPattern').resolves(false) + }) + + it('should fail with `NO_SPEC_PATTERN_MATCH`', async () => { + const result = await ctx.actions.project.runSpec({ specPath: 'e2e/abc.cy.ts' }) + + sinon.assert.match(result, { + code: 'NO_SPEC_PATTERN_MATCH', + detailMessage: sinon.match.string, + }) + }) + }) + + context('spec file not found', () => { + beforeEach(() => { + ctx.coreData.currentProject = '/cy-project' + sinon.stub(ctx.project, 'matchesSpecPattern').withArgs(sinon.match.string, 'e2e').resolves(true) + sinon.stub(ctx.fs, 'existsSync').returns(false) + }) + + it('should fail with `SPEC_NOT_FOUND`', async () => { + const result = await ctx.actions.project.runSpec({ specPath: 'e2e/abc.cy.ts' }) + + sinon.assert.match(result, { + code: 'SPEC_NOT_FOUND', + detailMessage: sinon.match.string, + }) + }) + }) + + context('matched testing type not configured', () => { + beforeEach(() => { + ctx.coreData.currentTestingType = null + ctx.coreData.currentProject = '/cy-project' + sinon.stub(ctx.project, 'matchesSpecPattern').withArgs(sinon.match.string, 'e2e').resolves(true) + sinon.stub(ctx.fs, 'existsSync').returns(true) + sinon.stub(ctx.lifecycleManager, 'isTestingTypeConfigured').withArgs('e2e').returns(false) + }) + + it('should fail with `TESTING_TYPE_NOT_CONFIGURED`', async () => { + const result = await ctx.actions.project.runSpec({ specPath: 'e2e/abc.cy.ts' }) + + sinon.assert.match(result, { + code: 'TESTING_TYPE_NOT_CONFIGURED', + detailMessage: sinon.match.string, + }) + }) + }) + + context('spec can be executed', () => { + beforeEach(() => { + ctx.coreData.currentProject = '/cy-project' + sinon.stub(ctx.project, 'matchesSpecPattern').withArgs(sinon.match.string, 'e2e').resolves(true) + sinon.stub(ctx.fs, 'existsSync').returns(true) + sinon.stub(ctx.project, 'getCurrentSpecByAbsolute').returns({ id: 'xyz' } as any) + sinon.stub(ctx.lifecycleManager, 'setInitialActiveBrowser') + ctx.coreData.activeBrowser = { id: 'abc' } as any + sinon.stub(ctx.lifecycleManager, 'setCurrentTestingType') + sinon.stub(ctx.actions.project, 'switchTestingTypesAndRelaunch') + ctx.coreData.app.browserStatus = 'open' + sinon.stub(ctx.emitter, 'subscribeTo').returns({ + next: () => {}, + return: () => {}, + } as any) + }) + + context('no current testing type', () => { + beforeEach(() => { + ctx.coreData.currentTestingType = null + sinon.stub(ctx.lifecycleManager, 'isTestingTypeConfigured').withArgs('e2e').returns(true) + }) + + it('should succeed', async () => { + const result = await ctx.actions.project.runSpec({ specPath: 'e2e/abc.cy.ts' }) + + sinon.assert.match(result, { + testingType: 'e2e', + browser: sinon.match.object, + spec: sinon.match.object, + }) + + expect(ctx.lifecycleManager.setCurrentTestingType).to.have.been.calledWith('e2e') + expect(ctx.actions.project.switchTestingTypesAndRelaunch).to.have.been.calledWith('e2e') + expect(ctx._apis.projectApi.runSpec).to.have.been.called + }) + }) + + context('testing type needs to change', () => { + beforeEach(() => { + ctx.coreData.currentTestingType = 'component' + sinon.stub(ctx.lifecycleManager, 'isTestingTypeConfigured').withArgs('e2e').returns(true) + }) + + it('should succeed', async () => { + const result = await ctx.actions.project.runSpec({ specPath: 'e2e/abc.cy.ts' }) + + sinon.assert.match(result, { + testingType: 'e2e', + browser: sinon.match.object, + spec: sinon.match.object, + }) + + expect(ctx.lifecycleManager.setCurrentTestingType).to.have.been.calledWith('e2e') + expect(ctx.actions.project.switchTestingTypesAndRelaunch).to.have.been.calledWith('e2e') + expect(ctx._apis.projectApi.runSpec).to.have.been.called + }) + }) + + context('testing type does not need to change', () => { + beforeEach(() => { + ctx.coreData.currentTestingType = 'e2e' + }) + + it('should succeed', async () => { + const result = await ctx.actions.project.runSpec({ specPath: 'e2e/abc.cy.ts' }) + + sinon.assert.match(result, { + testingType: 'e2e', + browser: sinon.match.object, + spec: sinon.match.object, + }) + + expect(ctx.lifecycleManager.setCurrentTestingType).not.to.have.been.called + expect(ctx.actions.project.switchTestingTypesAndRelaunch).not.to.have.been.called + + expect(ctx._apis.projectApi.runSpec).to.have.been.called + }) + }) + }) + }) }) diff --git a/packages/data-context/test/unit/helper.ts b/packages/data-context/test/unit/helper.ts index 417d74167e06..bc7bf3288afb 100644 --- a/packages/data-context/test/unit/helper.ts +++ b/packages/data-context/test/unit/helper.ts @@ -56,6 +56,7 @@ export function createTestDataContext (mode: DataContextConfig['mode'] = 'run', closeActiveProject: sinon.stub(), insertProjectToCache: sinon.stub().resolves(), getProjectRootsFromCache: sinon.stub().resolves([]), + runSpec: sinon.stub(), } as unknown as ProjectApiShape, electronApi: { isMainWindowFocused: sinon.stub().returns(false), diff --git a/packages/data-context/test/unit/sources/ProjectDataSource.spec.ts b/packages/data-context/test/unit/sources/ProjectDataSource.spec.ts index d29e5ffe690e..d50d56c4af9d 100644 --- a/packages/data-context/test/unit/sources/ProjectDataSource.spec.ts +++ b/packages/data-context/test/unit/sources/ProjectDataSource.spec.ts @@ -12,7 +12,7 @@ import { FoundSpec } from '@packages/types' import { DataContext } from '../../../src' import type { FindSpecs } from '../../../src/actions' import { createTestDataContext } from '../helper' -import { defaultSpecPattern } from '@packages/config' +import { defaultExcludeSpecPattern, defaultSpecPattern } from '@packages/config' import FixturesHelper from '@tooling/system-tests' chai.use(sinonChai) @@ -827,6 +827,7 @@ describe('ProjectDataSource', () => { it('yields correct jsx extension if there are jsx files and specPattern allows', async () => { sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: [defaultSpecPattern.component] }) + sinon.stub(ctx.project, 'specPatternsByTestingType').resolves({ specPattern: [defaultSpecPattern.component] }) const defaultSpecFileName = await ctx.project.defaultSpecFileName() @@ -835,6 +836,7 @@ describe('ProjectDataSource', () => { it('yields non-jsx extension if there are jsx files but specPattern disallows', async () => { sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: ['cypress/component/*.cy.js'] }) + sinon.stub(ctx.project, 'specPatternsByTestingType').resolves({ specPattern: ['cypress/component/*.cy.js'] }) const defaultSpecFileName = await ctx.project.defaultSpecFileName() @@ -843,4 +845,59 @@ describe('ProjectDataSource', () => { }) }) }) + + describe('specPatternsByTestingType', () => { + context('when custom patterns configured', () => { + beforeEach(() => { + sinon.stub(ctx.lifecycleManager, 'getConfigFileContents').resolves({ + e2e: { + specPattern: 'abc', + excludeSpecPattern: 'def', + }, + component: { + specPattern: 'uvw', + excludeSpecPattern: 'xyz', + } as any, + }) + }) + + it('should return custom e2e patterns', async () => { + expect(await ctx.project.specPatternsByTestingType('e2e')).to.eql({ + specPattern: ['abc'], + excludeSpecPattern: ['def'], + }) + }) + + it('should return custom component patterns', async () => { + expect(await ctx.project.specPatternsByTestingType('component')).to.eql({ + specPattern: ['uvw'], + excludeSpecPattern: ['xyz'], + }) + }) + }) + + context('when no custom patterns configured', () => { + const wrapInArray = (value: string | string[]): string[] => { + return Array.isArray(value) ? value : [value] + } + + beforeEach(() => { + sinon.stub(ctx.lifecycleManager, 'getConfigFileContents').resolves({}) + }) + + it('should return default e2e patterns', async () => { + expect(await ctx.project.specPatternsByTestingType('e2e')).to.eql({ + specPattern: wrapInArray(defaultSpecPattern.e2e), + excludeSpecPattern: wrapInArray(defaultExcludeSpecPattern.e2e), + }) + }) + + it('should return default component patterns', async () => { + expect(await ctx.project.specPatternsByTestingType('component')).to.eql({ + specPattern: wrapInArray(defaultSpecPattern.component), + excludeSpecPattern: wrapInArray(defaultExcludeSpecPattern.component), + }) + }) + }) + }) }) diff --git a/packages/graphql/schemas/schema.graphql b/packages/graphql/schemas/schema.graphql index 1fbab1c5811c..572d6ada5b25 100644 --- a/packages/graphql/schemas/schema.graphql +++ b/packages/graphql/schemas/schema.graphql @@ -1748,6 +1748,16 @@ type Mutation { """Reset the Wizard to the starting position""" resetWizard: Boolean! + + """ + Run a single spec file using a supplied path. This initiates but does not wait for completion of the requested spec run. + """ + runSpec( + """ + Relative path of spec to run from Cypress project root - must match e2e or component specPattern + """ + specPath: String! + ): RunSpecResult scaffoldTestingType: Query setAndLoadCurrentTestingType(testingType: TestingTypeEnum!): Query @@ -2178,6 +2188,39 @@ enum RunInstanceStatusEnum { UNCLAIMED } +"""Error encountered during a runSpec mutation""" +type RunSpecError { + code: RunSpecErrorCode! + + """ + Contextual information for the error (typically an expanded error message) + """ + detailMessage: String +} + +enum RunSpecErrorCode { + GENERAL_ERROR + NO_PROJECT + NO_SPEC_PATH + NO_SPEC_PATTERN_MATCH + SPEC_NOT_FOUND + TESTING_TYPE_NOT_CONFIGURED +} + +"""Result of a runSpec mutation""" +type RunSpecResponse { + """Browser test was launched in""" + browser: Browser! + + """Matched spec that was launched""" + spec: Spec! + + """Testing Type that spec was launched in""" + testingType: String! +} + +union RunSpecResult = RunSpecError | RunSpecResponse + """A file that we just added to the filesystem during project setup""" type ScaffoldedFile { """Info about the file we just scaffolded""" diff --git a/packages/graphql/src/schemaTypes/enumTypes/gql-RunSpecErrorCodeEnum.ts b/packages/graphql/src/schemaTypes/enumTypes/gql-RunSpecErrorCodeEnum.ts new file mode 100644 index 000000000000..244d9ad4f0e1 --- /dev/null +++ b/packages/graphql/src/schemaTypes/enumTypes/gql-RunSpecErrorCodeEnum.ts @@ -0,0 +1,8 @@ +import { enumType } from 'nexus' + +export const RunSpecErrorCode = ['NO_PROJECT', 'NO_SPEC_PATH', 'NO_SPEC_PATTERN_MATCH', 'TESTING_TYPE_NOT_CONFIGURED', 'SPEC_NOT_FOUND', 'GENERAL_ERROR'] as const + +export const RunSpecErrorCodeEnum = enumType({ + name: 'RunSpecErrorCode', + members: RunSpecErrorCode, +}) diff --git a/packages/graphql/src/schemaTypes/enumTypes/index.ts b/packages/graphql/src/schemaTypes/enumTypes/index.ts index f16606e3e88b..c3be7ed3f035 100644 --- a/packages/graphql/src/schemaTypes/enumTypes/index.ts +++ b/packages/graphql/src/schemaTypes/enumTypes/index.ts @@ -8,5 +8,6 @@ export * from './gql-ErrorTypeEnum' export * from './gql-FileExtensionEnum' export * from './gql-PreferencesTypeEnum' export * from './gql-ProjectEnums' +export * from './gql-RunSpecErrorCodeEnum' export * from './gql-SpecEnum' export * from './gql-WizardEnums' diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts index 92a919b3b381..3e7f04e8121f 100644 --- a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts +++ b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts @@ -1,8 +1,6 @@ import { arg, booleanArg, enumType, idArg, mutationType, nonNull, stringArg, list, intArg } from 'nexus' import { Wizard } from './gql-Wizard' -import { CodeGenTypeEnum } from '../enumTypes/gql-CodeGenTypeEnum' -import { TestingTypeEnum } from '../enumTypes/gql-WizardEnums' -import { PreferencesTypeEnum } from '../enumTypes/gql-PreferencesTypeEnum' +import { CodeGenTypeEnum, TestingTypeEnum, PreferencesTypeEnum } from '../enumTypes' import { FileDetailsInput } from '../inputTypes/gql-FileDetailsInput' import { WizardUpdateInput } from '../inputTypes/gql-WizardUpdateInput' import { CurrentProject } from './gql-CurrentProject' @@ -13,6 +11,7 @@ import { ScaffoldedFile } from './gql-ScaffoldedFile' import debugLib from 'debug' import { ReactComponentResponse } from './gql-ReactComponentResponse' import { TestsBySpecInput } from '../inputTypes' +import { RunSpecResult } from '../unions' const debug = debugLib('cypress:graphql:mutation') @@ -634,6 +633,21 @@ export const mutation = mutationType({ }, }) + t.field('runSpec', { + description: 'Run a single spec file using a supplied path. This initiates but does not wait for completion of the requested spec run.', + type: RunSpecResult, + args: { + specPath: nonNull(stringArg({ + description: 'Relative path of spec to run from Cypress project root - must match e2e or component specPattern', + })), + }, + resolve: async (source, args, ctx) => { + return await ctx.actions.project.runSpec({ + specPath: args.specPath, + }) + }, + }) + t.field('dismissWarning', { type: Query, args: { diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-RunSpecError.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-RunSpecError.ts new file mode 100644 index 000000000000..117cb51f7916 --- /dev/null +++ b/packages/graphql/src/schemaTypes/objectTypes/gql-RunSpecError.ts @@ -0,0 +1,16 @@ +import { objectType } from 'nexus' +import { RunSpecErrorCodeEnum } from '../enumTypes' + +export const RunSpecError = objectType({ + name: 'RunSpecError', + description: 'Error encountered during a runSpec mutation', + definition (t) { + t.nonNull.field('code', { + type: RunSpecErrorCodeEnum, + }) + + t.string('detailMessage', { + description: 'Contextual information for the error (typically an expanded error message)', + }) + }, +}) diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-RunSpecResponse.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-RunSpecResponse.ts new file mode 100644 index 000000000000..5e2961fbf525 --- /dev/null +++ b/packages/graphql/src/schemaTypes/objectTypes/gql-RunSpecResponse.ts @@ -0,0 +1,21 @@ +import { objectType } from 'nexus' + +export const RunSpecResponse = objectType({ + name: 'RunSpecResponse', + description: 'Result of a runSpec mutation', + definition (t) { + t.nonNull.string('testingType', { + description: 'Testing Type that spec was launched in', + }) + + t.nonNull.field('browser', { + type: 'Browser', + description: 'Browser test was launched in', + }) + + t.nonNull.field('spec', { + type: 'Spec', + description: 'Matched spec that was launched', + }) + }, +}) diff --git a/packages/graphql/src/schemaTypes/objectTypes/index.ts b/packages/graphql/src/schemaTypes/objectTypes/index.ts index 61323f52854c..c91e335b9df0 100644 --- a/packages/graphql/src/schemaTypes/objectTypes/index.ts +++ b/packages/graphql/src/schemaTypes/objectTypes/index.ts @@ -24,6 +24,8 @@ export * from './gql-Query' export * from './gql-ReactComponentDescriptor' export * from './gql-ReactComponentResponse' export * from './gql-RelevantRun' +export * from './gql-RunSpecError' +export * from './gql-RunSpecResponse' export * from './gql-ScaffoldedFile' export * from './gql-Spec' export * from './gql-Subscription' diff --git a/packages/graphql/src/schemaTypes/unions/gql-RunSpecResult.ts b/packages/graphql/src/schemaTypes/unions/gql-RunSpecResult.ts new file mode 100644 index 000000000000..9398e9bcb12b --- /dev/null +++ b/packages/graphql/src/schemaTypes/unions/gql-RunSpecResult.ts @@ -0,0 +1,14 @@ +import { unionType } from 'nexus' + +export const RunSpecResult = unionType({ + name: 'RunSpecResult', + definition (t) { + t.members( + 'RunSpecResponse', + 'RunSpecError', + ) + }, + resolveType: (obj) => { + return 'code' in obj ? 'RunSpecError' : 'RunSpecResponse' + }, +}) diff --git a/packages/graphql/src/schemaTypes/unions/index.ts b/packages/graphql/src/schemaTypes/unions/index.ts index 66f93711cb1b..1cca8e1aba0c 100644 --- a/packages/graphql/src/schemaTypes/unions/index.ts +++ b/packages/graphql/src/schemaTypes/unions/index.ts @@ -2,3 +2,4 @@ // created by autobarrel, do not modify directly export * from './gql-GeneratedSpecResult' +export * from './gql-RunSpecResult' diff --git a/packages/server/lib/makeDataContext.ts b/packages/server/lib/makeDataContext.ts index 82857e8bc6cb..a3cab7acf137 100644 --- a/packages/server/lib/makeDataContext.ts +++ b/packages/server/lib/makeDataContext.ts @@ -161,6 +161,9 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext { resetServer () { return openProject.getProject()?.server.reset() }, + async runSpec (spec: Cypress.Spec): Promise { + openProject.changeUrlToSpec(spec) + }, }, electronApi: { openExternal (url: string) { diff --git a/packages/server/lib/open_project.ts b/packages/server/lib/open_project.ts index 46e4199fc9fc..7706321fa97a 100644 --- a/packages/server/lib/open_project.ts +++ b/packages/server/lib/open_project.ts @@ -223,6 +223,8 @@ export class OpenProject { changeUrlToSpec (spec: Cypress.Spec) { if (!this.projectBase) { + debug('No projectBase, cannot change url') + return } From 7d060571ca8c13e4ad45016fd4247b966255dbfd Mon Sep 17 00:00:00 2001 From: Ely Lucas Date: Wed, 24 May 2023 06:38:13 -0600 Subject: [PATCH 08/13] chore: replace gitter badge with discord on readme (#26771) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8df5cd1a3ef7..9867193933f2 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ npm - - Gitter chat + + Discord chat StackShare From b1f699a0a7be9ed77950ac3565d70389be3193f0 Mon Sep 17 00:00:00 2001 From: Adam Stone-Lord Date: Wed, 24 May 2023 16:10:42 +0000 Subject: [PATCH 09/13] chore: add GraphQL mutation for sending system notifications via Electron (#26773) --- .../data-context/src/actions/ElectronActions.ts | 17 ++++++++++++++++- packages/graphql/schemas/schema.graphql | 3 +++ .../src/schemaTypes/objectTypes/gql-Mutation.ts | 15 ++++++++++++++- packages/server/lib/makeDataContext.ts | 3 +++ packages/server/lib/modes/interactive.ts | 5 +++++ 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/data-context/src/actions/ElectronActions.ts b/packages/data-context/src/actions/ElectronActions.ts index afce5ab557ad..091e81d54677 100644 --- a/packages/data-context/src/actions/ElectronActions.ts +++ b/packages/data-context/src/actions/ElectronActions.ts @@ -1,4 +1,4 @@ -import type { App, BrowserWindow, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'electron' +import type { App, BrowserWindow, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue, Notification } from 'electron' import os from 'os' import type { DataContext } from '..' import _ from 'lodash' @@ -13,6 +13,7 @@ export interface ElectronApiShape { copyTextToClipboard(text: string): void isMainWindowFocused(): boolean focusMainWindow(): void + createNotification(title: string, body: string): Notification } export class ElectronActions { @@ -104,4 +105,18 @@ export class ElectronActions { return obj.filePath || null }) } + + showSystemNotification (title: string, body: string, onClick?: () => void) { + const notification = this.ctx.electronApi.createNotification(title, body) + + const defaultOnClick = async () => { + await this.ctx.actions.browser.focusActiveBrowserWindow() + } + + const clickHandler = onClick || defaultOnClick + + notification.on('click', clickHandler) + + notification.show() + } } diff --git a/packages/graphql/schemas/schema.graphql b/packages/graphql/schemas/schema.graphql index 572d6ada5b25..051f97132614 100644 --- a/packages/graphql/schemas/schema.graphql +++ b/packages/graphql/schemas/schema.graphql @@ -1786,6 +1786,9 @@ type Mutation { """Set failed tests for the current run to be used by the runner""" setTestsForRun(testsBySpec: [TestsBySpecInput!]!): Boolean + """Show system notification via Electron""" + showSystemNotification(body: String!, title: String!): Boolean + """Switch Testing type and relaunch browser""" switchTestingTypeAndRelaunch(testingType: TestingTypeEnum!): Boolean diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts index 3e7f04e8121f..c29d019bc02e 100644 --- a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts +++ b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts @@ -775,6 +775,19 @@ export const mutation = mutationType({ }, }) + t.boolean('showSystemNotification', { + description: 'Show system notification via Electron', + args: { + title: nonNull(stringArg()), + body: nonNull(stringArg()), + }, + resolve: async (source, args, ctx) => { + ctx.actions.electron.showSystemNotification(args.title, args.body) + + return true + }, + }) + t.boolean('moveToRelevantRun', { description: 'Allow the relevant run for debugging marked as next to be considered the current relevant run', args: { @@ -787,7 +800,7 @@ export const mutation = mutationType({ }, }) - //Using a mutation to just return data in order to be able to await the results in the component + // Using a mutation to just return data in order to be able to await the results in the component t.list.nonNull.string('testsForRun', { description: 'Return the set of test titles for the given spec path', args: { diff --git a/packages/server/lib/makeDataContext.ts b/packages/server/lib/makeDataContext.ts index a3cab7acf137..74b7349df09c 100644 --- a/packages/server/lib/makeDataContext.ts +++ b/packages/server/lib/makeDataContext.ts @@ -189,6 +189,9 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext { focusMainWindow () { return focusMainWindow() }, + createNotification (title, body) { + return new electron.Notification({ title, body }) + }, }, localSettingsApi: { async setPreferences (object: AllowedState) { diff --git a/packages/server/lib/modes/interactive.ts b/packages/server/lib/modes/interactive.ts index fe7227cf6369..b76912ca5dd3 100644 --- a/packages/server/lib/modes/interactive.ts +++ b/packages/server/lib/modes/interactive.ts @@ -157,6 +157,11 @@ export = { }, async run (options: LaunchArgs, _loading: Promise) { + // Need to set this for system notifications to appear as "Cypress" on Windows + if (app.setAppUserModelId) { + app.setAppUserModelId('Cypress') + } + // Note: We do not await the `_loading` promise here since initializing // the data context can significantly delay initial render of the UI // https://github.com/cypress-io/cypress/issues/26388#issuecomment-1492616609 From d91177a0f343c14f42b3cd786d7ae45289496ae9 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 24 May 2023 17:16:16 +0100 Subject: [PATCH 10/13] fix: upgrade typescript from 4.7.4 to 4.9.5 (#26826) Snyk has created this PR to upgrade typescript from 4.7.4 to 4.9.5. See this package in npm: See this project in Snyk: https://app.snyk.io/org/cypress-opensource/project/d5b36925-e6ee-455d-9649-6560a9aca413?utm_source=github&utm_medium=referral&page=upgrade-pr --- .../config-cjs-and-esm/config-with-ts-module/package.json | 2 +- .../config-cjs-and-esm/config-with-ts-module/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/system-tests/projects/config-cjs-and-esm/config-with-ts-module/package.json b/system-tests/projects/config-cjs-and-esm/config-with-ts-module/package.json index 6019ddbb6d0a..03af11608963 100644 --- a/system-tests/projects/config-cjs-and-esm/config-with-ts-module/package.json +++ b/system-tests/projects/config-cjs-and-esm/config-with-ts-module/package.json @@ -1,7 +1,7 @@ { "dependencies": { "find-up": "6.3.0", - "typescript": "4.7.4" + "typescript": "4.9.5" }, "type": "module", "projectFixtureDirectory": "simple_passing" diff --git a/system-tests/projects/config-cjs-and-esm/config-with-ts-module/yarn.lock b/system-tests/projects/config-cjs-and-esm/config-with-ts-module/yarn.lock index 6b28faefe620..06992131d2a9 100644 --- a/system-tests/projects/config-cjs-and-esm/config-with-ts-module/yarn.lock +++ b/system-tests/projects/config-cjs-and-esm/config-with-ts-module/yarn.lock @@ -36,10 +36,10 @@ path-exists@^5.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7" integrity "sha1-pqrZSJIAsh+rMeSc8JJ35RFvuec= sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==" -typescript@4.7.4: - version "4.7.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" - integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== +typescript@4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== yocto-queue@^1.0.0: version "1.0.0" From 850973e7095c5d0d1af7c2ca7af015088c1ce3c6 Mon Sep 17 00:00:00 2001 From: Stokes Player Date: Thu, 25 May 2023 13:27:17 -0400 Subject: [PATCH 11/13] test: fix 2 broken tests for Windows (#26854) --- .circleci/workflows.yml | 2 +- packages/app/cypress/e2e/specs_list_e2e.cy.ts | 4 +++- packages/data-context/src/actions/ProjectActions.ts | 7 +++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index ec0582d3dc10..5e0ac591593c 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -72,7 +72,7 @@ windowsWorkflowFilters: &windows-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: [ 'ryanm/feat/unify-cdp-approach-in-electron', << pipeline.git.branch >> ] + - equal: [ 'stokes/fix_windows_e2e_test', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> diff --git a/packages/app/cypress/e2e/specs_list_e2e.cy.ts b/packages/app/cypress/e2e/specs_list_e2e.cy.ts index df59656ffd44..a8a98ed481a3 100644 --- a/packages/app/cypress/e2e/specs_list_e2e.cy.ts +++ b/packages/app/cypress/e2e/specs_list_e2e.cy.ts @@ -88,7 +88,9 @@ describe('App: Spec List (E2E)', () => { it('lists files after folders when in same directory', () => { cy.findAllByTestId('row-directory-depth-2').first().click() - cy.get('[id="speclist-cypress/e2e/admin_users/"]') + const rowId = getPathForPlatform('speclist-cypress/e2e/admin_users/').replaceAll('\\', '\\\\') + + cy.get(`[id="${rowId}"]`) .next() .should('contain', 'admin.user') .next() diff --git a/packages/data-context/src/actions/ProjectActions.ts b/packages/data-context/src/actions/ProjectActions.ts index d5d28934b2e9..254b14cad479 100644 --- a/packages/data-context/src/actions/ProjectActions.ts +++ b/packages/data-context/src/actions/ProjectActions.ts @@ -9,7 +9,7 @@ import type { ProjectShape } from '../data/coreDataShape' import type { DataContext } from '..' import { hasNonExampleSpec } from '../codegen' import templates from '../codegen/templates' -import { insertValuesInConfigFile } from '../util' +import { insertValuesInConfigFile, toPosix } from '../util' import { getError } from '@packages/errors' import { resetIssuedWarnings } from '@packages/config' import type { RunSpecErrorCode } from '@packages/graphql/src/schemaTypes' @@ -606,9 +606,12 @@ export class ProjectActions { // Now that we're in the correct testingType, verify the requested spec actually exists // We don't have specs available until a testingType is loaded, so even through we validated // a matching file exists above it may not end up loading as a valid spec so we validate that here - const spec = this.ctx.project.getCurrentSpecByAbsolute(absoluteSpecPath) + // + // Have to use toPosix here to align windows absolute paths with how the absolute path is storied in the data context + const spec = this.ctx.project.getCurrentSpecByAbsolute(toPosix(absoluteSpecPath)) if (!spec) { + debug(`Spec not found with path: ${absoluteSpecPath}`) throw new RunSpecError('SPEC_NOT_FOUND', `Unable to find matching spec with path ${absoluteSpecPath}`) } From 0cf11fe4167a2f447994bc279afbd2cfcb93c46f Mon Sep 17 00:00:00 2001 From: Ben M Date: Fri, 26 May 2023 08:55:38 -0400 Subject: [PATCH 12/13] Update stale_issues_and_pr_cleanup.yml Upped the number of operations per run. Have been manually doing that so this job can get through all the issues in the repo with no problems. --- .github/workflows/stale_issues_and_pr_cleanup.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale_issues_and_pr_cleanup.yml b/.github/workflows/stale_issues_and_pr_cleanup.yml index 444fefc26cbb..503ce803c021 100644 --- a/.github/workflows/stale_issues_and_pr_cleanup.yml +++ b/.github/workflows/stale_issues_and_pr_cleanup.yml @@ -9,7 +9,7 @@ on: max-operations-per-run: description: 'max operations per run' required: false - default: 800 + default: 3000 days-before-stale: description: 'days-before-stale' required: false @@ -33,7 +33,7 @@ permissions: pull-requests: write env: DEFAULT_DEBUG_ONLY: true - DEFAULT_MAX_OPS: 800 + DEFAULT_MAX_OPS: 3000 DEFAULT_DAYS_BEFORE_STALE: 180 DEFAULT_DAYS_BEFORE_CLOSE: 14 DEFAULT_EXEMPT_ISSUE_LABELS: 'type: feature,type: enhancement,routed-to-e2e,routed-to-ct,routed-to-tools,routed-to-cloud,prevent-stale,triaged' From d34a148472d53b02d24d626d09563669556c0769 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Fri, 26 May 2023 18:27:52 +0100 Subject: [PATCH 13/13] chore(dep): [Snyk] Upgrade vite from 2.9.13 to 2.9.15 (#26830) Co-authored-by: Ben M --- .../projects/vite-ct-object-api/package.json | 2 +- .../projects/vite-ct-object-api/yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/system-tests/projects/vite-ct-object-api/package.json b/system-tests/projects/vite-ct-object-api/package.json index 1f47e6b16f6a..b5a6942d2c3e 100644 --- a/system-tests/projects/vite-ct-object-api/package.json +++ b/system-tests/projects/vite-ct-object-api/package.json @@ -6,6 +6,6 @@ "@testing-library/cypress": "9.0.0", "ansi-regex": "4.1.1", "typescript": "4.2.3", - "vite": "2.9.13" + "vite": "2.9.15" } } diff --git a/system-tests/projects/vite-ct-object-api/yarn.lock b/system-tests/projects/vite-ct-object-api/yarn.lock index 502aa16f433e..3c6f683fd3a0 100644 --- a/system-tests/projects/vite-ct-object-api/yarn.lock +++ b/system-tests/projects/vite-ct-object-api/yarn.lock @@ -370,10 +370,10 @@ resolve@^1.22.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -rollup@^2.59.0: - version "2.70.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.1.tgz#824b1f1f879ea396db30b0fc3ae8d2fead93523e" - integrity sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA== +"rollup@>=2.59.0 <2.78.0": + version "2.77.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12" + integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g== optionalDependencies: fsevents "~2.3.2" @@ -406,14 +406,14 @@ typescript@4.2.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== -vite@2.9.13: - version "2.9.13" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.13.tgz#859cb5d4c316c0d8c6ec9866045c0f7858ca6abc" - integrity sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw== +vite@2.9.15: + version "2.9.15" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.15.tgz#2858dd5b2be26aa394a283e62324281892546f0b" + integrity sha512-fzMt2jK4vQ3yK56te3Kqpkaeq9DkcZfBbzHwYpobasvgYmP2SoAr6Aic05CsB4CzCZbsDv4sujX3pkEGhLabVQ== dependencies: esbuild "^0.14.27" postcss "^8.4.13" resolve "^1.22.0" - rollup "^2.59.0" + rollup ">=2.59.0 <2.78.0" optionalDependencies: fsevents "~2.3.2"