Merge branch 'develop' into release/15.0.0

This commit is contained in:
Jennifer Shehane
2025-04-01 10:36:23 -04:00
committed by GitHub
17 changed files with 176 additions and 61 deletions

View File

@@ -15,6 +15,7 @@ _Released 4/8/2025 (PENDING)_
- Allows for `babel-loader` version 10 to be a peer dependency of `@cypress/webpack-preprocessor`. Fixed in [#31218](https://github.com/cypress-io/cypress/pull/31218).
- Fixed an issue where Firefox BiDi was prematurely removing prerequests on pending requests. Fixes [#31376](https://github.com/cypress-io/cypress/issues/31376).
- Fixed an [issue](https://github.com/electron/electron/issues/45398) with Electron causing slow animations and increased test times by starting a CDP screencast with a noop configuration. Fixes [#30980](https://github.com/cypress-io/cypress/issues/30980).
**Misc:**
@@ -23,7 +24,7 @@ _Released 4/8/2025 (PENDING)_
**Dependency Updates:**
- Upgraded `mocha` from `7.0.1` to `7.1.1`. Addressed in [#31401](https://github.com/cypress-io/cypress/pull/31401).
- Upgraded `mocha` from `7.0.1` to `7.1.2`. Addressed in [#31416](https://github.com/cypress-io/cypress/pull/31416).
- Upgraded `webdriver` from `9.7.3` to `9.11.0`. Addressed in [#31315](https://github.com/cypress-io/cypress/pull/31315).
- Upgraded `win-version-info` from `5.0.1` to `6.0.1`. Addressed in [#31358](https://github.com/cypress-io/cypress/pull/31358).

View File

@@ -0,0 +1,29 @@
import { loadSpec } from './support/spec-loader'
describe('event-manager', () => {
it('emits the cypress:created event when spec is rerun', () => {
// load the spec initially
loadSpec({
filePath: 'hooks/basic.cy.js',
passCount: 1,
})
cy.window().then((win) => {
const eventManager = win.getEventManager()
let eventReceived = false
// listen for the cypress:created event
eventManager.on('cypress:created', (cypress) => {
expect(cypress).to.exist
expect(cypress).to.not.equal(win.Cypress)
eventReceived = true
})
// trigger a rerun
cy.get('.restart').click()
// keep retrying until eventReceived becomes true
cy.wrap(() => eventReceived).invoke('call').should('be.true')
})
})
})

View File

@@ -56,6 +56,8 @@ describe('Cypress Studio', () => {
cy.waitForSpecToFinish()
cy.findByTestId('studio-panel').should('not.exist')
cy.intercept('/cypress/e2e/index.html', () => {
// wait for the promise to resolve before responding
// this will ensure the studio panel is loaded before the test finishes
@@ -70,7 +72,7 @@ describe('Cypress Studio', () => {
// regular studio is not loaded until after the test finishes
cy.get('[data-cy="hook-name-studio commands"]').should('not.exist')
// cloud studio is loaded immediately
cy.findByTestId('studio-panel').should('exist').then(() => {
cy.findByTestId('studio-panel').then(() => {
// we've verified the studio panel is loaded, now resolve the promise so the test can finish
deferred.resolve()
})
@@ -81,8 +83,8 @@ describe('Cypress Studio', () => {
cy.waitForSpecToFinish()
// Verify the studio panel is still open
cy.findByTestId('studio-panel').should('exist')
cy.get('[data-cy="hook-name-studio commands"]').should('exist')
cy.findByTestId('studio-panel')
cy.get('[data-cy="hook-name-studio commands"]')
})
})
@@ -1031,11 +1033,11 @@ describe('studio functionality', () => {
})`)
})
cy.findByTestId('studio-toolbar-controls').should('exist')
cy.findByTestId('studio-toolbar-controls')
cy.get('button').contains('Save Commands').click()
cy.findByTestId('studio-toolbar-controls').should('exist')
cy.findByTestId('studio-toolbar-controls')
cy.get('button').contains('Save Commands')
cy.findByTestId('hook-name-studio commands').closest('.hook-studio').within(() => {

View File

@@ -57,7 +57,7 @@
<div
v-show="showPanel4"
data-cy="studio-panel"
data-cy="panel-4"
class="h-full bg-gray-100 relative"
:style="{width: `${panel4Width}px`}"
>

View File

@@ -1,9 +1,11 @@
<template>
<StudioInstructionsModal
v-if="studioStore.instructionModalIsOpen"
:open="studioStore.instructionModalIsOpen"
@close="studioStore.closeInstructionModal"
/>
<StudioSaveModal
v-if="studioStore.saveModalIsOpen"
:open="studioStore.saveModalIsOpen"
@close="studioStore.closeSaveModal"
/>
@@ -99,7 +101,8 @@
</template>
<template #panel4>
<StudioPanel
v-show="shouldShowStudioPanel"
v-if="shouldShowStudioPanel"
data-cy="studio-panel"
:can-access-studio-a-i="studioStore.canAccessStudioAI"
/>
</template>

View File

@@ -432,9 +432,9 @@ export class EventManager {
}
Cypress = this.Cypress = this.$CypressDriver.create(config)
this.localBus.emit('cypress:created', Cypress)
// expose Cypress globally
// @ts-ignore
window.Cypress = Cypress
this.studioStore.setup(config)

View File

@@ -4,7 +4,7 @@
</div>
<div
v-else
ref="root"
ref="container"
>
Loading the panel...
</div>
@@ -27,26 +27,30 @@ const props = defineProps<{
interface StudioApp { default: StudioAppDefaultShape }
const root = ref<HTMLElement | null>(null)
const container = ref<HTMLElement | null>(null)
const error = ref<string | null>(null)
const Panel = ref<StudioPanelShape | null>(null)
const ReactStudioPanel = ref<StudioPanelShape | null>(null)
const reactRoot = ref<Root | null>(null)
const maybeRenderReactComponent = () => {
if (!Panel.value || !!error.value) {
// don't render the react component if the react studio panel has not loaded or if there is an error
if (!ReactStudioPanel.value || !!error.value) {
return
}
const panel = window.UnifiedRunner.React.createElement(Panel.value, { canAccessStudioAI: props.canAccessStudioAI })
const panel = window.UnifiedRunner.React.createElement(ReactStudioPanel.value, { canAccessStudioAI: props.canAccessStudioAI })
if (!reactRoot.value) {
reactRoot.value = window.UnifiedRunner.ReactDOM.createRoot(container.value)
}
reactRoot.value = window.UnifiedRunner.ReactDOM.createRoot(root.value)
reactRoot.value?.render(panel)
}
watch(() => props.canAccessStudioAI, maybeRenderReactComponent)
const unmountReactComponent = () => {
if (!Panel.value || !root.value) {
if (!ReactStudioPanel.value || !container.value) {
return
}
@@ -86,7 +90,7 @@ loadRemote<StudioApp>('app-studio').then((module) => {
return
}
Panel.value = module.default.StudioPanel
ReactStudioPanel.value = module.default.StudioPanel
maybeRenderReactComponent()
}).catch((e) => {
error.value = e.message

View File

@@ -77,7 +77,7 @@
"methods": "1.1.2",
"mime": "^3.0.0",
"minimatch": "3.1.2",
"mocha": "7.1.1",
"mocha": "7.1.2",
"multer": "1.4.4",
"ordinal": "1.0.3",
"react-15.6.1": "npm:react@15.6.1",

View File

@@ -1,9 +1,20 @@
diff --git a/node_modules/mocha/CHANGELOG.md b/node_modules/mocha/CHANGELOG.md
deleted file mode 100644
index 8c2f86a..0000000
index c3b33f1..0000000
--- a/node_modules/mocha/CHANGELOG.md
+++ /dev/null
@@ -1,623 +0,0 @@
@@ -1,634 +0,0 @@
-# 7.1.2 / 2020-04-26
-
-## :nut_and_bolt: Other
-
-- [#4251](https://github.com/mochajs/mocha/issues/4251): Prevent karma-mocha from stalling ([**@juergba**](https://github.com/juergba))
-- [#4222](https://github.com/mochajs/mocha/issues/4222): Update dependency mkdirp to v0.5.5 ([**@outsideris**](https://github.com/outsideris))
-
-## :book: Documentation
-
-- [#4208](https://github.com/mochajs/mocha/issues/4208): Add Wallaby logo to site ([**@boneskull**](https://github.com/boneskull))
-
-# 7.1.1 / 2020-03-18
-
-## :lock: Security Fixes
@@ -760,10 +771,10 @@ index 740e1fd..0cd2769 100644
runner.fullStackTrace = options.fullTrace;
runner.asyncOnly = options.asyncOnly;
diff --git a/node_modules/mocha/lib/runner.js b/node_modules/mocha/lib/runner.js
index 8e7c873..5208e60 100644
index 25c03b9..f71c992 100644
--- a/node_modules/mocha/lib/runner.js
+++ b/node_modules/mocha/lib/runner.js
@@ -682,9 +682,43 @@ Runner.prototype.runTests = function(suite, fn) {
@@ -677,9 +677,43 @@ Runner.prototype.runTests = function(suite, fn) {
}
self.emit(constants.EVENT_TEST_END, test);
return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
@@ -809,7 +820,7 @@ index 8e7c873..5208e60 100644
var clonedTest = test.clone();
clonedTest.currentRetry(retry + 1);
tests.unshift(clonedTest);
@@ -694,8 +728,25 @@ Runner.prototype.runTests = function(suite, fn) {
@@ -689,8 +723,25 @@ Runner.prototype.runTests = function(suite, fn) {
// Early return + hook trigger so that it doesn't
// increment the count wrong
return self.hookUp(HOOK_TYPE_AFTER_EACH, next);

View File

@@ -14,6 +14,7 @@ interface Props {
containerRef?: RefObject<HTMLDivElement>
contentClass?: string
hideExpander: boolean
children?: ReactNode
}
interface State {

View File

@@ -6,6 +6,7 @@ import events from './events'
interface Props {
fileDetails: FileDetails
className?: string
children?: React.ReactNode
}
// Catches click events that bubble from children and emits open file events to

View File

@@ -17,6 +17,7 @@ import type { CDPSocketServer } from '@packages/socket/lib/cdp-socket'
import memory from './memory'
import { BrowserCriClient } from './browser-cri-client'
import { getRemoteDebuggingPort } from '../util/electron-app'
import type { CriClient } from './cri-client'
// TODO: unmix these two types
type ElectronOpts = Windows.WindowOptions & BrowserLaunchOpts
@@ -131,6 +132,25 @@ async function recordVideo (cdpAutomation: CdpAutomation, videoApi: RunModeVideo
await cdpAutomation.startVideoRecording(writeVideoFrame, screencastOpts())
}
// Start video legitimately if we have a video api. Otherwise, if we're in run mode, start a dummy screencast to prevent:
// https://github.com/electron/electron/issues/45398
async function handleVideo (handleVideoOptions: { pageCriClient: CriClient, cdpAutomation: CdpAutomation, videoApi?: RunModeVideoApi, options: BrowserLaunchOpts }) {
const { pageCriClient, cdpAutomation, videoApi, options } = handleVideoOptions
if (videoApi) {
await recordVideo(cdpAutomation, videoApi)
} else if (options.isTextTerminal) {
// To prevent https://github.com/electron/electron/issues/45398, we start a dummy screen cast with a quality of 0
// and only capture every 2^32 - 1 frames without listening to any frames. This is effectively a no-op, but it
// prevents the issue from occurring.
await pageCriClient.send('Page.startScreencast', {
format: 'jpeg',
everyNthFrame: 2 ** 31 - 1,
quality: 0,
})
}
}
export = {
_defaultOptions (projectRoot: string | undefined, state: Preferences, options: BrowserLaunchOpts, automation: Automation): ElectronOpts {
const _this = this
@@ -317,7 +337,7 @@ export = {
pageCriClient.send('ServiceWorker.enable'),
this.connectProtocolToBrowser({ protocolManager }),
cdpSocketServer?.attachCDPClient(cdpAutomation),
videoApi && recordVideo(cdpAutomation, videoApi),
handleVideo({ pageCriClient, cdpAutomation, videoApi, options }),
this._handleDownloads(win, options.downloadsFolder, automation),
utils.initializeCDP(pageCriClient, automation),
// Ensure to clear browser state in between runs. This is handled differently in browsers when we launch new tabs, but we don't have that concept in electron

View File

@@ -14,6 +14,7 @@ const { Automation } = require(`../../../lib/automation`)
const { BrowserCriClient } = require('../../../lib/browsers/browser-cri-client')
const electronApp = require('../../../lib/util/electron-app')
const utils = require('../../../lib/browsers/utils')
const { screencastOpts } = require('../../../lib/browsers/cdp_automation')
const ELECTRON_PID = 10001
@@ -476,6 +477,41 @@ describe('lib/browsers/electron', () => {
})
})
it('expects the video to be fully enabled if specified in the config', async function () {
const mockWriteVideoFrame = sinon.stub()
const mockVideoApi = {
useFfmpegVideoController: sinon.stub().resolves({
writeVideoFrame: mockWriteVideoFrame,
}),
}
await electron._launch(this.win, this.url, this.automation, this.options, mockVideoApi, undefined, { attachCDPClient: sinon.stub() })
expect(mockVideoApi.useFfmpegVideoController).to.be.called
expect(this.pageCriClient.on).to.be.calledWith('Page.screencastFrame', sinon.match.func)
expect(this.pageCriClient.send).to.be.calledWith('Page.startScreencast', screencastOpts())
})
it('starts the screencast but does not capture the frames if video is not enabled but the app is in run mode', async function () {
this.options.isTextTerminal = true
await electron._launch(this.win, this.url, this.automation, this.options, undefined, undefined, { attachCDPClient: sinon.stub() })
expect(this.pageCriClient.on).not.to.be.calledWith('Page.screencastFrame', sinon.match.func)
expect(this.pageCriClient.send).to.be.calledWith('Page.startScreencast', {
format: 'jpeg',
everyNthFrame: 2 ** 31 - 1,
quality: 0,
})
})
it('does not start the screencast if video is not enabled and the app is not in run mode', async function () {
await electron._launch(this.win, this.url, this.automation, this.options, undefined, undefined, { attachCDPClient: sinon.stub() })
expect(this.pageCriClient.on).not.to.be.calledWith('Page.screencastFrame', sinon.match.func)
expect(this.pageCriClient.send).not.to.be.calledWith('Page.startScreencast', sinon.match.any)
})
it('registers onRequest automation middleware and calls show when requesting to be focused', function () {
sinon.spy(this.automation, 'use')

View File

@@ -0,0 +1,9 @@
describe('electron animation bug', () => {
it('loads in less than .3 seconds', { defaultCommandTimeout: 750 }, () => {
cy.visit('/electron_animation_bug.html')
cy.get('#app').should('exist')
cy.get('#remove').click()
cy.get('#app').should('not.exist')
})
})

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<div id="app">
<div>hello</div>
<div>world</div>
</div>
<button id="remove">remove</button>
<script>
const remove = document.getElementById('remove')
remove.addEventListener('click', () => {
const app = document.getElementById('app')
app.animate([
{ transform: 'translate(5px, 10px)', opacity: 0 }
], {
duration: 500,
easing: 'cubic-bezier(0.4, 0, 1, 1)'
}).finished.then(() => {
app.remove()
})
})
</script>
</body>
</html>

View File

@@ -0,0 +1,9 @@
import { default as systemTests } from '../lib/system-tests'
describe('e2e electron animation bug', () => {
systemTests.it('executes a test that demonstrates the electron animation bug and ensures that we have worked around it', {
browser: 'electron',
project: 'e2e',
spec: 'electron_animation_bug.cy.ts',
})
})

View File

@@ -22793,13 +22793,6 @@ mkdirp@0.5.1:
dependencies:
minimist "0.0.8"
mkdirp@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.3.tgz#5a514b7179259287952881e94410ec5465659f8c"
integrity sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==
dependencies:
minimist "^1.2.5"
mkdirp@0.5.5:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
@@ -23030,36 +23023,6 @@ mocha@7.1.0:
yargs-parser "13.1.1"
yargs-unparser "1.6.0"
mocha@7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.1.tgz#89fbb30d09429845b1bb893a830bf5771049a441"
integrity sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==
dependencies:
ansi-colors "3.2.3"
browser-stdout "1.3.1"
chokidar "3.3.0"
debug "3.2.6"
diff "3.5.0"
escape-string-regexp "1.0.5"
find-up "3.0.0"
glob "7.1.3"
growl "1.10.5"
he "1.2.0"
js-yaml "3.13.1"
log-symbols "3.0.0"
minimatch "3.0.4"
mkdirp "0.5.3"
ms "2.1.1"
node-environment-flags "1.0.6"
object.assign "4.1.0"
strip-json-comments "2.0.1"
supports-color "6.0.0"
which "1.3.1"
wide-align "1.1.3"
yargs "13.3.2"
yargs-parser "13.1.2"
yargs-unparser "1.6.0"
mocha@7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.2.tgz#8e40d198acf91a52ace122cd7599c9ab857b29e6"