Files
cypress/packages/server/test/unit/open_project_spec.js
Ryan Manuel df047b0a42 feat: cy.prompt experimental command [Feature Flagged] (#31752)
* Create cy-prompt-development.md

* chore: cy prompt infrastructure (#31748)

* feat: cy prompt infrastructure

* refactor and add tests

* refactor

* rename experimental config

* prompt

* fix test

* Update cy-prompt-development.md

* Update cy-prompt-development.md

* PR comments

* Update packages/server/lib/cloud/api/cy-prompt/get_cy_prompt_bundle.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* PR comments

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix test

* Delete packages/server/lib/cloud/StudioLifecycleManager.ts

* Delete packages/server/test/unit/cloud/StudioLifecycleManager_spec.ts

* chore: add cdp connection to cy prompt (#31806)

* chore: add cdp connection to cy prompt

* minor fix

* fix type build

* try to fix build

* Update packages/server/lib/browsers/browser-cri-client.ts

Co-authored-by: Bill Glesias <bglesias@gmail.com>

* Update packages/server/lib/browsers/browser-cri-client.ts

Co-authored-by: Bill Glesias <bglesias@gmail.com>

* do not support prompt in firefox or webkit

* rework timing of lifecycle

* refactor

* fix tests

* troubleshooting

* troubleshooting

* fix tests

* additional troubleshooting

* additional troubleshooting

* additional troubleshooting

* attempt to fix build

* add back

* debugging

* debugging

* debugging

* debugging

* clean up

* fix unit tests

* rework

---------

Co-authored-by: Bill Glesias <bglesias@gmail.com>

* chore: create infrastructure to support backend function in cy.prompt (#31803)

* chore: add promptBackend as an additional Cypress-attached function

* Update packages/app/src/runner/event-manager.ts

* update types

* fix types

* fix spacing

* refactor

* additional refactor

* fix type build

* fix build

* refactor

* reword messages

* fix

* debugging

* undo debugging

* PR comment

* fix tests

* fix tests

* fix tests

* fix test

* chore: add watcher for cy-prompt development (#31810)

* chore: add watcher for cy-prompt development

* test caching

* fix types

* chore: turn on beta deployments for cy-prompt

* internal: (cy.prompt) handle errors better in the command definition (#31835)

* internal: (cy.prompt) handle errors better in the command definition

* internal: (cy.prompt) add timeout and handle loading errors more cleanly

* add process environment variable

* clean up test

* update JSDoc

* chore: handle errors (#31854)

* chore: handle errors

* Fix ts, add test

* Fix error title

* Fix ts

* Fix ts

* chores: (cy.prompt) refactor routing to support both app and driver (#31891)

* chore: Share error utils with the cloud (#31887)

* share error utils with cloud

* additional rework

* Fix command, add isOpenMode

* Add / fix test

* fix ts

---------

Co-authored-by: Ryan Manuel <ryanm@cypress.io>

* internal: (cy.prompt) add infrastructure to support a Get Code modal (#31904)

* chore: (cy.prompt) add infrastructure to support a Get Code modal

* fix tests

* fix code paths

* Update eject button styles

* handle errors

* update types

* Update packages/server/lib/socket-base.ts

* Fix cy test

* update readme

---------

Co-authored-by: estrada9166 <estrada9166@gmail.com>

* chore: (cy.prompt) refactor getTestsState to take a runnable id (#31965)

* chore: (cy.prompt) refactor getTestsState to take a runnable id

* fix tests

* minor tweak

* chore: (cy.prompt) add manifest for all of the cloud delivered files (#31922)

* chore: (cy.prompt) add manifest for all of the cloud delivered files

* fix tests and remove environment variables

* update strategy

* fix build

* rework

* require manifest

* clean up

* refactor

* refactor

* Update packages/server/lib/cloud/cy-prompt/CyPromptLifecycleManager.ts

Co-authored-by: Matt Schile <mschile@cypress.io>

* fix test

---------

Co-authored-by: Matt Schile <mschile@cypress.io>

* feat: add cy prompt more info needed modal (WIP) (#31970)

* feat: add cy prompt more info needed modal

* Reset promptStore

* additional things exposed for more info

* rework

* fix tests

* fix build

* fix types

* fix types

* Update packages/app/src/runner/event-manager.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* reefactor

* chore: (cy.prompt) rework the file save lifecycle

* rework types

* add unit tests

---------

Co-authored-by: estrada9166 <estrada9166@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: (cy.prompt) ensure that we do not attach a vue ref to the react root in the cy.prompt modals (#32011)

* fix: (studio) ensure that we do not attach a vue ref to the react root in the studio panel

* fix test

* chore: Handle cy-prompt error to the cloud (#32009)

Co-authored-by: Ryan Manuel <ryanm@cypress.io>

* chore: display prompt error message (#32016)

* add createCloudRequest to cloudApi, handle errors during createCyPromptServer

* render the get code button on failure

* chore: update types

* chore: move CT and browser error to cloud (#32077)

* feat: (cy.prompt) give cy.prompt access to recording information (#32110)

* feat: (cy.prompt) give cy.prompt access to recording information

* undo bad refactor

* undo bad refactor

* undo bad refactor

* rename

* fix typo

* Update packages/server/test/unit/modes/record_spec.js

* Update packages/server/test/unit/modes/record_spec.js

* Update packages/server/test/unit/modes/record_spec.js

* Update packages/server/test/unit/modes/record_spec.js

* Update packages/server/test/unit/modes/record_spec.js

* Update cy-prompt-development.md

* Update cy-prompt-development.md

* Update cy-prompt-development.md

* chore: add CYPRESS_INTERNAL_SIMULATE_OPEN_MODE to simulate running Cypress tests in open mode (#32114)

* chore: (cy.prompt) ensure to strip out paths from all data when reporting errors in prompt (#32134)

* fix: (cy.prompt) ensure to strip out paths from all data when reporting errors in prompt

* refactor

* update tests

* fix logic error with protocol

* add event manager to get code

* Apply suggestions from code review

* fix: (cy.prompt) improve the get project options performance by running promises in parallel (#32196)

* fix: (cy.prompt) improve the get project options performance by running promises in parallel

* add awaits in test

* bump cache

* Update packages/server/lib/cloud/studio/StudioLifecycleManager.ts

* fix build

* chore: cleanup

* blank

* Update trigger-publish-binary-pipeline.js

* Update packages/server/test/unit/socket_spec.js

* get rid of environment variable

* fix: (cy.prompt) ensure to reset the prompt state when the event manager is torn down (indicating that we're no longer on the runner page) (#32301)

* fix: (cy.prompt) ensure to reset the prompt state when the event manager is torn down (indicating that we're no longer on the runner page)

* move test

* fix lint issue

* feat: (cy.prompt) introduce the concept of log collapse state being open/closed by default (#32262)

* feat: (cy.prompt) introduce the concept of logs' collapse state being open/closed by default

* update tests

* rename

* PR comments

* fix test due to bad merge of develop

* chore: Only allow experimentalPromptCommand within e2e config (#32435)

* chore: Only allow experimentalPromptCommand within e2e config

* Fix config

* Update tests

* Update test, update types

* Fix test and types

* fix tests

* Add types

* Update cli/types/cypress.d.ts

Co-authored-by: Ryan Manuel <ryanm@cypress.io>

---------

Co-authored-by: Ryan Manuel <ryanm@cypress.io>

* Update workflows.yml

* Add optional cyPromptManagerPromise property

* Implement error handling in CyPromptManager

Added a new method to handle errors uniformly in CyPromptManager.

* Fix indentation in socket_spec.js

* fix: (cy.prompt) handle when the prompt is executed by in CT or when the experiment is not enabled (#32470)

* fix: (cy.prompt) handle when the prompt is executed by in CT or when the experiment is not enabled

* Update packages/driver/cypress/e2e/commands/prompt/prompt.cy.ts

* Update packages/driver/cypress/e2e/commands/prompt/prompt.cy.ts

* update name of errors

* Bump cache version to 9-15-2025

* update yarn.lock

* fixes

* fix snapshot

* fix snapshot

* internal: fix get code button to be purple with correct margin (#32504)

* feat: (cy.prompt) update the types of prompt (#32529)

* fix: move currentMoreInfoNeededModalInfo.onCancel to onClose (#32559)

* internal: (cy.prompt) various improvements to error messages and time outs (#32582)

* internal: (cy.prompt) various improvements to error messages and timeouts

* fix test

* remove dead code

* Update packages/driver/src/cy/commands/prompt/index.ts

* update prompt experiment description

* fix build

* fix tests

* fix tests

* fix test

* update error message

* Update packages/server/lib/experiments.ts

Co-authored-by: Tim Griesser <tgriesser10@gmail.com>

---------

Co-authored-by: Tim Griesser <tgriesser10@gmail.com>

* internal: (cy.prompt) ensure that get code can properly work with prompts in cy.origin (#32596)

* internal: (cy.prompt) ensure that get code can properly work with prompts in cy.origin

* additional tests

* additional tests

* Apply suggestions from code review

* code review comment

* fix merge

* fix merge

* Update packages/data-context/graphql/schemaTypes/index.ts

* update workflow

* blank

* persist binaries

* chore: display custom link title (#32567)

* chore: display custom link title

* Add test

* Pass docs as second argument of the error

* Revert changes

* Use openExternal for links

---------

Co-authored-by: Jennifer Shehane <jennifer@cypress.io>

* update

* internal: (cy.prompt) do not retry on cert failures (#32624)

* internal: (cy.prompt) do not retry on cert failures

* tests and clean up

* additional cleanup and add error message for prompt

* fix unit tests

* Update report_studio_error.ts

* Update packages/network/lib/agent.ts

* cursor comment

* fix build

* fix build

* fix test

* fix build

* fix build

* fix build

* fix build

* fix build

* revert

* Update packages/driver/src/cypress/error_messages.ts

* Update packages/driver/cypress/e2e/commands/prompt/prompt-initialization-error.cy.ts

* fix build

* cursor comment

* add changelog entry

* clean up

* Update packages/app/src/store/prompt-store.ts

* Bump cache version to 10-3-2025

Updated cache version to trigger CI cache recreation.

* Revise CHANGELOG for new features and fixes

Updated changelog with new features and bug fixes.

* index on feat/cy-prompt: 3cbcf6c336 Bump cache version to 10-3-2025

* index on feat/cy-prompt: 3cbcf6c336 Bump cache version to 10-3-2025

* index on feat/cy-prompt: 3cbcf6c336 Bump cache version to 10-3-2025

* blank

* fix build

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Bill Glesias <bglesias@gmail.com>
Co-authored-by: Alejandro Estrada <estrada9166@gmail.com>
Co-authored-by: Matt Schile <mschile@cypress.io>
Co-authored-by: Tim Griesser <tgriesser10@gmail.com>
Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
Co-authored-by: Jennifer Shehane <shehane.jennifer@gmail.com>
Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com>
2025-10-04 09:01:00 -05:00

279 lines
9.8 KiB
JavaScript

require('../spec_helper')
const Bluebird = require('bluebird')
const browsers = require(`../../lib/browsers`)
const ProjectBase = require(`../../lib/project-base`).ProjectBase
const { openProject } = require('../../lib/open_project')
const preprocessor = require(`../../lib/plugins/preprocessor`).default
const runEvents = require(`../../lib/plugins/run_events`).default
const Fixtures = require('@tooling/system-tests')
const delay = require('lodash/delay')
const todosPath = Fixtures.projectPath('todos')
describe('lib/open_project', () => {
beforeEach(function () {
this.automation = {
reset: sinon.stub(),
use: sinon.stub(),
}
this.config = {
excludeSpecPattern: '**/*.nope',
projectRoot: todosPath,
proxyServer: 'http://cy-proxy-server',
}
this.onError = sinon.stub()
sinon.stub(browsers, 'get').resolves()
sinon.stub(browsers, 'open')
sinon.stub(browsers, 'connectToNewSpec')
sinon.stub(ProjectBase.prototype, 'initializeConfig').resolves({
specPattern: 'cypress/integration/**/*',
})
sinon.stub(ProjectBase.prototype, 'open').resolves()
sinon.stub(ProjectBase.prototype, 'reset').resolves()
sinon.stub(ProjectBase.prototype, 'getConfig').returns(this.config)
sinon.stub(ProjectBase.prototype, 'getAutomation').returns(this.automation)
sinon.stub(preprocessor, 'removeFile')
return Fixtures.scaffoldProject('todos').then(() => {
return openProject.create(todosPath, { testingType: 'e2e' }, { onError: this.onError })
})
})
context('#launch', () => {
beforeEach(async function () {
await openProject.create(todosPath, { testingType: 'e2e' }, { onError: this.onError })
openProject.getProject().__setConfig({
browserUrl: 'http://localhost:8888/__/',
projectRoot: todosPath,
specType: 'integration',
e2e: {
specPattern: 'cypress/integration/**/*',
},
})
openProject.getProject().options = {
onError: this.onError,
}
this.spec = {
absolute: 'path/to/spec',
relative: 'path/to/spec',
}
this.browser = { name: 'chrome' }
})
it('tells preprocessor to remove file on browser close', function () {
return openProject.launch(this.browser, this.spec)
.then(() => {
browsers.open.lastCall.args[1].onBrowserClose()
expect(preprocessor.removeFile).to.be.calledWith('path/to/spec')
})
})
it('does not tell preprocessor to remove file if no spec', function () {
return openProject.launch(this.browser, {})
.then(() => {
browsers.open.lastCall.args[1].onBrowserClose()
expect(preprocessor.removeFile).not.to.be.called
})
})
it('runs original onBrowserClose callback on browser close', function () {
const onBrowserClose = sinon.stub()
const options = { onBrowserClose }
return openProject.launch(this.browser, this.spec, options)
.then(() => {
browsers.open.lastCall.args[1].onBrowserClose()
expect(onBrowserClose).to.be.called
})
})
it('calls project.reset on launch', function () {
return openProject.launch(this.browser, this.spec)
.then(() => {
expect(ProjectBase.prototype.reset).to.be.called
})
})
it('sets isHeaded + isHeadless if not already defined', function () {
expect(this.browser.isHeaded).to.be.undefined
expect(this.browser.isHeadless).to.be.undefined
return openProject.launch(this.browser, this.spec)
.then(() => {
expect(this.browser.isHeaded).to.be.true
expect(this.browser.isHeadless).to.be.false
})
})
describe('spec events', function () {
this.beforeEach(function () {
sinon.stub(runEvents, 'execute').resolves()
})
it('executes after:spec on browser close if in interactive mode', function () {
this.config.experimentalInteractiveRunEvents = true
this.config.isTextTerminal = false
const onBrowserClose = () => Bluebird.resolve()
return openProject.launch(this.browser, this.spec, { onBrowserClose })
.then(() => {
return browsers.open.lastCall.args[1].onBrowserClose()
})
.then(() => {
expect(runEvents.execute).to.be.calledWith('after:spec', this.spec)
})
})
it('does not execute after:spec on browser close if not in interactive mode', function () {
this.config.experimentalInteractiveRunEvents = true
this.config.isTextTerminal = true
const onBrowserClose = () => Bluebird.resolve()
return openProject.launch(this.browser, this.spec, { onBrowserClose })
.then(() => {
return browsers.open.lastCall.args[1].onBrowserClose()
})
.then(() => {
expect(runEvents.execute).not.to.be.calledWith('after:spec')
})
})
it('does not execute after:spec on browser close if experimental flag is not enabled', function () {
this.config.experimentalInteractiveRunEvents = false
this.config.isTextTerminal = false
const onBrowserClose = () => Bluebird.resolve()
return openProject.launch(this.browser, this.spec, { onBrowserClose })
.then(() => {
return browsers.open.lastCall.args[1].onBrowserClose()
})
.then(() => {
expect(runEvents.execute).not.to.be.calledWith('after:spec')
})
})
it('does not execute after:spec on browser close if the project is no longer open', function () {
this.config.experimentalInteractiveRunEvents = true
this.config.isTextTerminal = false
const onBrowserClose = () => Bluebird.resolve()
return openProject.launch(this.browser, this.spec, { onBrowserClose })
.then(() => {
openProject.__reset()
return browsers.open.lastCall.args[1].onBrowserClose()
})
.then(() => {
expect(runEvents.execute).not.to.be.calledWith('after:spec')
})
})
it('sends after:spec errors through onError option', function () {
// TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23448
this.retries(15)
const err = new Error('thrown from after:spec handler')
this.config.experimentalInteractiveRunEvents = true
this.config.isTextTerminal = false
runEvents.execute.withArgs('after:spec').rejects(err)
return openProject.launch(this.browser, this.spec, { onError: this.onError })
.then(() => {
return browsers.open.lastCall.args[1].onBrowserClose()
})
.then(() => {
return new Bluebird((res) => {
delay(() => {
expect(runEvents.execute).to.be.calledWith('after:spec')
expect(this.onError).to.be.calledWith(err)
res()
}, 100)
})
})
})
it('calls connectToNewSpec when shouldLaunchNewTab is set and the browser is not electron', async function () {
await openProject.launch(this.browser, this.spec, { shouldLaunchNewTab: true })
expect(browsers.connectToNewSpec.lastCall.args[0]).to.be.equal(this.browser)
})
it('calls open when shouldLaunchNewTab is set and the browser is electron', async function () {
await openProject.launch({ name: 'electron' }, this.spec, { shouldLaunchNewTab: true })
expect(browsers.open).to.have.been.calledOnce
})
})
})
context('#sendFocusBrowserMessage', () => {
it('focuses browser if runner is connected', async () => {
// Stubbing out relaunchBrowser function created during launch
openProject.relaunchBrowser = sinon.stub()
sinon.stub(ProjectBase.prototype, 'isRunnerSocketConnected').returns(true)
sinon.stub(ProjectBase.prototype, 'sendFocusBrowserMessage').resolves()
await openProject.sendFocusBrowserMessage()
expect(ProjectBase.prototype.isRunnerSocketConnected).to.have.been.calledOnce
expect(ProjectBase.prototype.sendFocusBrowserMessage).to.have.been.calledOnce
expect(openProject.relaunchBrowser).not.to.have.been.called
})
it('relaunches browser if runner is not connected and relaunch exists', async () => {
// Stubbing out relaunchBrowser function created during launch
openProject.relaunchBrowser = sinon.stub()
sinon.stub(ProjectBase.prototype, 'isRunnerSocketConnected').returns(false)
sinon.stub(ProjectBase.prototype, 'sendFocusBrowserMessage').resolves()
await openProject.sendFocusBrowserMessage()
expect(ProjectBase.prototype.isRunnerSocketConnected).to.have.been.calledOnce
expect(ProjectBase.prototype.sendFocusBrowserMessage).not.to.have.been.called
expect(openProject.relaunchBrowser).to.have.been.calledOnce
})
it('does not throw if relaunch is not defined', async () => {
// Stubbing out relaunchBrowser function created during launch
openProject.relaunchBrowser = null
sinon.stub(ProjectBase.prototype, 'isRunnerSocketConnected').returns(false)
sinon.stub(ProjectBase.prototype, 'sendFocusBrowserMessage').resolves()
await openProject.sendFocusBrowserMessage()
expect(ProjectBase.prototype.isRunnerSocketConnected).to.have.been.calledOnce
expect(ProjectBase.prototype.sendFocusBrowserMessage).not.to.have.been.called
})
})
context('#connectProtocolToBrowser', () => {
it('connects protocol to browser', async () => {
sinon.stub(browsers, 'connectProtocolToBrowser').resolves()
const options = sinon.stub()
await openProject.connectProtocolToBrowser(options)
expect(browsers.connectProtocolToBrowser).to.be.calledWith(options)
})
})
context('#connectCyPromptToBrowser', () => {
it('connects cy prompt to browser', async () => {
sinon.stub(browsers, 'connectCyPromptToBrowser').resolves()
const options = sinon.stub()
await openProject.connectCyPromptToBrowser(options)
})
})
})