mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-26 10:59:23 -06:00
Merge branch 'develop' into release/15.0.0
This commit is contained in:
@@ -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).
|
||||
|
||||
|
||||
29
packages/app/cypress/e2e/runner/event-manager.cy.ts
Normal file
29
packages/app/cypress/e2e/runner/event-manager.cy.ts
Normal 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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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`}"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
@@ -14,6 +14,7 @@ interface Props {
|
||||
containerRef?: RefObject<HTMLDivElement>
|
||||
contentClass?: string
|
||||
hideExpander: boolean
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
26
system-tests/projects/e2e/electron_animation_bug.html
Normal file
26
system-tests/projects/e2e/electron_animation_bug.html
Normal 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>
|
||||
9
system-tests/test/electron_animation_bug_spec.ts
Normal file
9
system-tests/test/electron_animation_bug_spec.ts
Normal 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',
|
||||
})
|
||||
})
|
||||
37
yarn.lock
37
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user