chore: remove experimentalPromptCommand config (#33497)

* chore: remove experimentalPromptCommand config

* Update with code review

* feat: add projectId handling in CyPromptLifecycleManager and ProjectBase

- Enhanced ProjectBase to include projectId in the context.
- Updated CyPromptLifecycleManager to utilize projectId, with fallback handling when project configuration fails.
- Added tests to verify behavior when project configuration is unavailable, ensuring fallback projectId is used correctly.

* Update with code review

* Update with code review
This commit is contained in:
Alejandro Estrada
2026-03-22 06:52:04 -05:00
committed by GitHub
parent b21fa13daa
commit 1bf3ff8023
37 changed files with 196 additions and 348 deletions
-5
View File
@@ -3355,11 +3355,6 @@ declare namespace Cypress {
* @default false
*/
experimentalOriginDependencies?: boolean
/**
* Enables support for `cy.prompt`, an AI-powered command that turns natural language steps into executable Cypress test code.
* @default false
*/
experimentalPromptCommand?: boolean
}
/**
-1
View File
@@ -22,7 +22,6 @@ Regardless of running against local or deployed `cy.prompt`:
- Run `yarn`
- Run `yarn cypress:open`
- Log In to the Cloud via the App
- Open a project that has `experimentalPromptCommand: true` set in the config of the `cypress.config.js|ts` file within `e2e`. Ensure the project has the feature flag `cy-prompt` enabled.
To run against a deployed version of `cy.prompt`:
+3 -5
View File
@@ -453,11 +453,9 @@ export class EventManager {
this._addListeners()
if (Cypress.config('experimentalPromptCommand')) {
await new Promise((resolve) => {
this.ws.emit('prompt:reset', resolve)
})
}
await new Promise((resolve) => {
this.ws.emit('prompt:reset', resolve)
})
}
isBrowserFamily (family: string) {
+6 -17
View File
@@ -28,6 +28,7 @@ const BREAKING_OPTION_ERROR_KEY: Readonly<AllCypressErrorNames[]> = [
'VIDEO_UPLOAD_ON_PASSES_REMOVED',
'RENAMED_CONFIG_OPTION',
'EXPERIMENTAL_STUDIO_REMOVED',
'EXPERIMENTAL_PROMPT_COMMAND_REMOVED',
'CYPRESS_ENV_DEPRECATION',
] as const
@@ -256,12 +257,6 @@ const driverConfigOptions: Array<DriverConfigOption> = [
isExperimental: true,
overrideLevel: 'any',
requireRestartOnChange: 'browser',
}, {
name: 'experimentalPromptCommand',
defaultValue: false,
validation: validate.isBoolean,
isExperimental: true,
requireRestartOnChange: 'server',
}, {
name: 'experimentalSourceRewriting',
defaultValue: false,
@@ -659,6 +654,11 @@ export const breakingOptions: Readonly<BreakingOption[]> = [
errorKey: 'EXPERIMENTAL_STUDIO_REMOVED',
isWarning: true,
},
{
name: 'experimentalPromptCommand',
errorKey: 'EXPERIMENTAL_PROMPT_COMMAND_REMOVED',
isWarning: true,
},
{
name: 'allowCypressEnv',
errorKey: 'CYPRESS_ENV_DEPRECATION',
@@ -711,12 +711,6 @@ export const breakingRootOptions: Array<BreakingOption> = [
isWarning: false,
testingTypes: ['e2e'],
},
{
name: 'experimentalPromptCommand',
errorKey: 'EXPERIMENTAL_PROMPT_COMMAND_E2E_ONLY',
isWarning: false,
testingTypes: ['e2e'],
},
{
name: 'justInTimeCompile',
errorKey: 'JIT_COMPONENT_TESTING',
@@ -764,11 +758,6 @@ export const testingTypeBreakingOptions: { e2e: Array<BreakingOption>, component
errorKey: 'EXPERIMENTAL_ORIGIN_DEPENDENCIES_E2E_ONLY',
isWarning: false,
},
{
name: 'experimentalPromptCommand',
errorKey: 'EXPERIMENTAL_PROMPT_COMMAND_E2E_ONLY',
isWarning: false,
},
{
name: 'injectDocumentDomain',
errorKey: 'INJECT_DOCUMENT_DOMAIN_E2E_ONLY',
@@ -7,6 +7,7 @@ exports[`config/src/index > .getBreakingKeys > returns list of breaking config k
"experimentalSkipDomainInjection",
"videoUploadOnPasses",
"experimentalStudio",
"experimentalPromptCommand",
"allowCypressEnv",
]
`;
@@ -45,7 +46,6 @@ exports[`config/src/index > .getDefaultValues > returns list of public config ke
"experimentalMemoryManagement": false,
"experimentalModifyObstructiveThirdPartyCode": false,
"experimentalOriginDependencies": false,
"experimentalPromptCommand": false,
"experimentalRunAllSpecs": false,
"experimentalSingleTabRunMode": false,
"experimentalSourceRewriting": false,
@@ -143,7 +143,6 @@ exports[`config/src/index > .getDefaultValues > returns list of public config ke
"experimentalMemoryManagement": false,
"experimentalModifyObstructiveThirdPartyCode": false,
"experimentalOriginDependencies": false,
"experimentalPromptCommand": false,
"experimentalRunAllSpecs": false,
"experimentalSingleTabRunMode": false,
"experimentalSourceRewriting": false,
@@ -231,7 +230,6 @@ exports[`config/src/index > .getPublicConfigKeys > returns list of public config
"experimentalModifyObstructiveThirdPartyCode",
"injectDocumentDomain",
"experimentalOriginDependencies",
"experimentalPromptCommand",
"experimentalSourceRewriting",
"experimentalSingleTabRunMode",
"experimentalWebKitSupport",
+20
View File
@@ -162,6 +162,26 @@ describe('config/src/index', () => {
expect(errorFn).toHaveBeenCalledTimes(0)
})
it('calls warning callback if config contains experimentalPromptCommand', () => {
const warningFn = vi.fn()
const errorFn = vi.fn()
configUtil.validateNoBreakingConfig({
experimentalPromptCommand: true,
configFile: 'config.js',
}, warningFn, errorFn, 'e2e')
expect(warningFn).toHaveBeenCalledExactlyOnceWith('EXPERIMENTAL_PROMPT_COMMAND_REMOVED', {
name: 'experimentalPromptCommand',
newName: undefined,
value: undefined,
testingType: 'e2e',
configFile: 'config.js',
})
expect(errorFn).toHaveBeenCalledTimes(0)
})
it('calls error callback if config contains breaking option that should throw an error', () => {
const warningFn = vi.fn()
const errorFn = vi.fn()
@@ -1193,7 +1193,6 @@ describe('config/src/project/utils', () => {
experimentalInteractiveRunEvents: { value: false, from: 'default' },
experimentalMemoryManagement: { value: false, from: 'default' },
experimentalOriginDependencies: { value: false, from: 'default' },
experimentalPromptCommand: { value: false, from: 'default' },
experimentalRunAllSpecs: { value: false, from: 'default' },
experimentalSingleTabRunMode: { value: false, from: 'default' },
experimentalSourceRewriting: { value: false, from: 'default' },
@@ -1318,7 +1317,6 @@ describe('config/src/project/utils', () => {
experimentalInteractiveRunEvents: { value: false, from: 'default' },
experimentalMemoryManagement: { value: false, from: 'default' },
experimentalOriginDependencies: { value: false, from: 'default' },
experimentalPromptCommand: { value: false, from: 'default' },
experimentalRunAllSpecs: { value: false, from: 'default' },
experimentalSingleTabRunMode: { value: false, from: 'default' },
experimentalSourceRewriting: { value: false, from: 'default' },
+1 -1
View File
@@ -1182,7 +1182,7 @@ enum ErrorTypeEnum {
ERROR_WRITING_FILE
EXPERIMENTAL_JIT_COMPILE_REMOVED
EXPERIMENTAL_ORIGIN_DEPENDENCIES_E2E_ONLY
EXPERIMENTAL_PROMPT_COMMAND_E2E_ONLY
EXPERIMENTAL_PROMPT_COMMAND_REMOVED
EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED
EXPERIMENTAL_SINGLE_TAB_RUN_MODE
EXPERIMENTAL_SKIP_DOMAIN_INJECTION_REMOVED
-1
View File
@@ -25,7 +25,6 @@ export const baseConfig: Cypress.ConfigOptions = {
configFile: '../../mocha-reporter-config.json',
},
e2e: {
experimentalPromptCommand: true,
experimentalOriginDependencies: true,
experimentalModifyObstructiveThirdPartyCode: true,
setupNodeEvents: (on, config) => {
+45 -74
View File
@@ -163,90 +163,61 @@ const initializeCloudCyPrompt = async (Cypress: Cypress.Cypress, cy: Cypress.Cyp
}
export default (Commands: Cypress.Cypress['Commands'], Cypress: Cypress.Cypress, cy: Cypress.Cypress['cy']) => {
// @ts-expect-error - these types are not yet implemented until the prompt command is rolled out
if (Cypress.config('experimentalPromptCommand')) {
let initializeCloudCyPromptPromise = initializeCloudCyPrompt(Cypress, cy)
let initializeCloudCyPromptPromise = initializeCloudCyPrompt(Cypress, cy)
const commands = {
prompt (steps: string[], commandOptions: object = {}) {
const promptCmd = cy.state('current')
const commands = {
prompt (steps: string[], commandOptions: object = {}) {
const promptCmd = cy.state('current')
const downloadTimeout = '_downloadTimeout' in commandOptions ? commandOptions._downloadTimeout as number : 45000
const downloadTimeout = '_downloadTimeout' in commandOptions ? commandOptions._downloadTimeout as number : 45000
let timeoutId: NodeJS.Timeout
const timeoutPromise = new Promise((resolve) => {
timeoutId = setTimeout(() => {
resolve({
error: undefined,
timedOut: true,
})
}, downloadTimeout)
})
const raceBundleResult = Promise.race([
initializeCloudCyPromptPromise,
timeoutPromise,
]).finally(() => {
clearTimeout(timeoutId)
}) as Promise<BundleResult>
return cy.wrap(raceBundleResult, { log: false, timeout: 1e9 }).then((bundleResult: BundleResult) => {
if (bundleResult.timedOut) {
cy.state('current', promptCmd)
return $errUtils.throwErrByPath('prompt.promptDownloadTimedOut', {
args: {
error: new Error('cy.prompt bundle download timed out'),
},
})
}
if (bundleResult.error) {
cy.state('current', promptCmd)
throw bundleResult.error
}
const cyPrompt = bundleResult.bundle
return cyPrompt({
steps,
commandOptions,
promptCmd,
let timeoutId: NodeJS.Timeout
const timeoutPromise = new Promise((resolve) => {
timeoutId = setTimeout(() => {
resolve({
error: undefined,
timedOut: true,
})
})
},
}
}, downloadTimeout)
})
const raceBundleResult = Promise.race([
initializeCloudCyPromptPromise,
timeoutPromise,
]).finally(() => {
clearTimeout(timeoutId)
}) as Promise<BundleResult>
commands.prompt['__resetPrompt'] = async (delay: number = 0) => {
initializedModule = null
initializeCloudCyPromptPromise = new Promise((resolve) => setTimeout(resolve, delay)).then(() => initializeCloudCyPrompt(Cypress, cy))
}
return cy.wrap(raceBundleResult, { log: false, timeout: 1e9 }).then((bundleResult: BundleResult) => {
if (bundleResult.timedOut) {
cy.state('current', promptCmd)
Commands.addAll(commands)
} else {
Commands.addAll({
prompt () {
const stack = cy.state('current').get('userInvocationStack')
if (Cypress.testingType === 'component') {
$errUtils.throwErrByPath('prompt.promptTestingTypeError', {
errProps: {
name: 'PromptTestingTypeError',
},
onFail: (err) => {
err.stack = stack
return $errUtils.throwErrByPath('prompt.promptDownloadTimedOut', {
args: {
error: new Error('cy.prompt bundle download timed out'),
},
})
}
$errUtils.throwErrByPath('prompt.experimentalPromptCommandError', {
errProps: {
name: 'PromptNotEnabledError',
},
onFail: (err) => {
err.stack = stack
},
if (bundleResult.error) {
cy.state('current', promptCmd)
throw bundleResult.error
}
const cyPrompt = bundleResult.bundle
return cyPrompt({
steps,
commandOptions,
promptCmd,
})
},
})
})
},
}
commands.prompt['__resetPrompt'] = async (delay: number = 0) => {
initializedModule = null
initializeCloudCyPromptPromise = new Promise((resolve) => setTimeout(resolve, delay)).then(() => initializeCloudCyPrompt(Cypress, cy))
}
Commands.addAll(commands)
}
@@ -1396,14 +1396,6 @@ export default {
`,
docsUrl: 'https://on.cypress.io/proxy-configuration',
},
promptTestingTypeError: stripIndent`\
\`cy.prompt\` is currently only supported in end-to-end tests.
`,
experimentalPromptCommandError: stripIndent`\
\`cy.prompt\` cannot be called without the \`experimentalPromptCommand\` being set.
Please set this in your Cypress config file to continue using \`cy.prompt\`.
`,
},
proxy: {
+6 -11
View File
@@ -1245,18 +1245,13 @@ export const AllCypressErrors = {
${fmt.code(code)}`
},
EXPERIMENTAL_PROMPT_COMMAND_E2E_ONLY: () => {
const code = errPartial`
{
e2e: {
experimentalPromptCommand: true
},
}`
EXPERIMENTAL_PROMPT_COMMAND_REMOVED: () => {
return errTemplate`\
The ${fmt.highlight(`experimentalPromptCommand`)} experiment is currently only supported for End to End Testing and must be configured as an e2e testing type property: ${fmt.highlightSecondary(`e2e.experimentalPromptCommand`)}.
${fmt.code(code)}`
The ${fmt.highlight(`experimentalPromptCommand`)} option was removed in ${fmt.cypressVersion(`15.13.0`)}.
\`cy.prompt\` is now available for all users.
You can safely remove this option from your config.`
},
JIT_COMPONENT_TESTING: () => {
return errTemplate`\
@@ -1,7 +0,0 @@
The experimentalPromptCommand experiment is currently only supported for End to End Testing and must be configured as an e2e testing type property: e2e.experimentalPromptCommand.

{
 e2e: {
 experimentalPromptCommand: true
 },
}
@@ -0,0 +1,5 @@
The experimentalPromptCommand option was removed in Cypress version 15.13.0.

`cy.prompt` is now available for all users.

You can safely remove this option from your config.
@@ -1068,6 +1068,12 @@ describe('visual error templates', () => {
}
},
EXPERIMENTAL_PROMPT_COMMAND_REMOVED: () => {
return {
default: [],
}
},
BROWSER_UNSUPPORTED_LAUNCH_OPTION: () => {
return {
default: ['electron', ['env']],
@@ -1080,12 +1086,6 @@ describe('visual error templates', () => {
}
},
EXPERIMENTAL_PROMPT_COMMAND_E2E_ONLY: () => {
return {
default: [],
}
},
PROXY_ENCOUNTERED_INVALID_HEADER_NAME: () => {
const err = makeErr()
@@ -614,10 +614,6 @@
"name": "Source rewriting",
"description": "Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement algorithm. See [#5273](https://github.com/cypress-io/cypress/issues/5273) for details."
},
"experimentalPromptCommand": {
"name": "Prompt command",
"description": "Enables support for `cy.prompt`, an AI-powered command that turns natural language steps into executable Cypress test code."
},
"experimentalWebKitSupport": {
"name": "WebKit Support",
"description": "Adds support for testing in the WebKit browser engine used by Safari. See https://on.cypress.io/webkit-experiment for more information."
@@ -182,25 +182,3 @@ describe('component testing dependency warnings', () => {
cy.get('[data-cy="warning-alert"]').should('not.exist')
})
})
describe('experimentalPromptCommand', () => {
it('is not a valid config for component testing', () => {
cy.scaffoldProject('experimentalPromptCommand')
cy.openProject('experimentalPromptCommand', ['--config-file', 'cypress-invalid-prompt-experiment.config.js'])
cy.visitLaunchpad()
cy.get('[data-cy-testingtype="component"]').click()
cy.findByTestId('error-header')
cy.contains('The experimentalPromptCommand experiment is currently only supported for End to End Testing')
})
it('is not a valid config when specified at root', () => {
cy.scaffoldProject('experimentalPromptCommand')
cy.openProject('experimentalPromptCommand', ['--config-file', 'cypress-invalid-prompt-experiment-root.config.js'])
cy.visitLaunchpad()
cy.get('[data-cy-testingtype="e2e"]').click()
cy.findByTestId('error-header')
cy.contains('The experimentalPromptCommand experiment is currently only supported for End to End Testing')
})
})
@@ -128,9 +128,7 @@ export default class Command extends Instrument {
}
get isCyPrompt () {
// @ts-expect-error - experimentalPromptCommand is not typed until we
// release the feature
return Cypress.config('experimentalPromptCommand') && this.name === 'prompt'
return this.name === 'prompt'
}
constructor (props: CommandProps) {
@@ -126,7 +126,6 @@ describe('commands', () => {
})
it('should render prompt get code button when state is passed', () => {
config.withArgs('experimentalPromptCommand').returns(true)
cy.mount(
<div>
<Command
@@ -153,7 +152,6 @@ describe('commands', () => {
})
it('should not render prompt get code button when state is failed with no error', () => {
config.withArgs('experimentalPromptCommand').returns(true)
cy.mount(
<div>
<Command
@@ -284,7 +282,6 @@ describe('commands', () => {
})
it('should not render prompt get code button when state is not passed', () => {
config.withArgs('experimentalPromptCommand').returns(true)
cy.mount(
<div>
<Command
@@ -308,19 +305,6 @@ describe('commands', () => {
cy.get('.command-prompt-get-code-indicator').should('not.exist')
})
it('should not render prompt if experimentalPromptCommand is false', () => {
config.withArgs('experimentalPromptCommand').returns(false)
cy.mount(
<div>
<Command model={new CommandModel({ name: 'prompt', state: 'passed', numElements: 1, hookId: '1', id: 1, testId: '1' })} scrollIntoView={() => {}} aliasesWithDuplicates={[]} />
</div>,
)
cy.get('.command-prompt-get-code').should('not.exist')
cy.get('.command-prompt-get-code-indicator').should('not.exist')
})
describe('Feedback button', () => {
const promptCommandModel = () => new CommandModel({
name: 'prompt',
@@ -331,10 +315,6 @@ describe('commands', () => {
testId: '1',
})
beforeEach(() => {
config.withArgs('experimentalPromptCommand').returns(true)
})
it('should render Feedback button when state is passed', () => {
cy.mount(
<div>
@@ -42,11 +42,17 @@ export class CyPromptLifecycleManager {
ctx,
record,
key,
projectId: fallbackProjectSlug,
}: {
cloudDataSource: CloudDataSource
ctx: DataContext
record?: boolean
key?: string
/**
* Resolved from ProjectBase config when cy-prompt initializes. Used when
* `ctx.project.getConfig()` is not available yet (no ProjectConfigManager).
*/
projectId?: string | null
}): void {
// Register this instance in the data context
ctx.update((data) => {
@@ -62,15 +68,25 @@ export class CyPromptLifecycleManager {
},
}
const resolveProjectSlug = async (): Promise<string | undefined> => {
try {
const config = await ctx.project.getConfig()
return config.projectId || undefined
} catch {
return fallbackProjectSlug || undefined
}
}
const getProjectOptions = async () => {
const [user, config] = await Promise.all([
const [user, projectSlug] = await Promise.all([
ctx.actions.auth.authApi.getUser(),
ctx.project.getConfig(),
resolveProjectSlug(),
])
return {
user,
projectSlug: config.projectId || undefined,
projectSlug,
record,
key,
isOpenMode: ctx.isOpenMode,
@@ -88,6 +104,14 @@ export class CyPromptLifecycleManager {
const cloudUrl = ctx.cloud.getCloudUrl(cloudEnv)
const cloudHeaders = await ctx.cloud.additionalHeaders()
let projectSlug: string | undefined
try {
projectSlug = (await ctx.project.getConfig()).projectId || undefined
} catch {
projectSlug = fallbackProjectSlug || undefined
}
reportCyPromptError({
cloudApi: {
cloudUrl,
@@ -98,7 +122,7 @@ export class CyPromptLifecycleManager {
},
additionalHeaders: cloudHeaders,
cyPromptHash: this.cyPromptHash,
projectSlug: (await ctx.project.getConfig()).projectId || undefined,
projectSlug,
error,
cyPromptMethod: 'initializeCyPromptManager',
cyPromptMethodArgs: [],
@@ -130,6 +154,12 @@ export class CyPromptLifecycleManager {
return cyPromptManager
}
resetCyPrompt (): void {
if (this.cyPromptManager) {
this.cyPromptManager.reset()
}
}
private async createCyPromptManager ({
cloudDataSource,
getProjectOptions,
-2
View File
@@ -59,7 +59,6 @@ const _summaries: StringValues = {
experimentalRunAllSpecs: 'Enables the "Run All Specs" UI feature, allowing the execution of multiple specs sequentially',
experimentalOriginDependencies: 'Enables support for `Cypress.require()` for including dependencies within the `cy.origin()` callback.',
experimentalMemoryManagement: 'Enables support for improved memory management within Chromium-based browsers.',
experimentalPromptCommand: 'Enables support for `cy.prompt`, an AI-powered command that turns natural language steps into executable Cypress test code.',
}
/**
@@ -81,7 +80,6 @@ const _names: StringValues = {
experimentalRunAllSpecs: 'Run All Specs',
experimentalOriginDependencies: 'Origin Dependencies',
experimentalMemoryManagement: 'Memory Management',
experimentalPromptCommand: 'Prompt Command',
}
/**
+7 -9
View File
@@ -169,16 +169,14 @@ export class ProjectBase extends EE {
process.chdir(this.projectRoot)
this._server = new ServerBase(cfg)
if (cfg.experimentalPromptCommand) {
const cyPromptLifecycleManager = new CyPromptLifecycleManager()
cyPromptLifecycleManager.initializeCyPromptManager({
cloudDataSource: this.ctx.cloud,
ctx: this.ctx,
record: this.options.record,
key: this.options.key,
})
}
new CyPromptLifecycleManager().initializeCyPromptManager({
cloudDataSource: this.ctx.cloud,
ctx: this.ctx,
record: this.options.record,
key: this.options.key,
projectId: cfg.projectId,
})
if ((!cfg.isTextTerminal || process.env.CYPRESS_INTERNAL_SIMULATE_OPEN_MODE) && this.testingType === 'e2e') {
const studioLifecycleManager = new StudioLifecycleManager()
+2 -4
View File
@@ -489,15 +489,13 @@ export class SocketBase implements SocketBroadcaster {
}
})
socket.on('prompt:reset', async (cb) => {
socket.on('prompt:reset', (cb) => {
try {
const cyPrompt = await getCtx().coreData.cyPromptLifecycleManager?.getCyPrompt()
// If we have runState, then we shouldn't reset the full prompt manager because
// we are just changing top. We will clear the prompt manager for a specific test
// later.
if (!runState) {
cyPrompt?.cyPromptManager?.reset()
getCtx().coreData.cyPromptLifecycleManager?.resetCyPrompt()
}
} finally {
cb()
@@ -249,7 +249,7 @@ describe('CyPromptLifecycleManager', () => {
await expect(getProjectOptions()).to.be.rejectedWith('getUser failed')
})
it('handles errors when getProjectConfig fails', async () => {
it('uses no project slug when getProjectConfig fails without fallback projectId', async () => {
cyPromptLifecycleManager.initializeCyPromptManager({
cloudDataSource: mockCloudDataSource,
ctx: mockCtx,
@@ -304,8 +304,40 @@ describe('CyPromptLifecycleManager', () => {
mockCtx.project.getConfig = sinon.stub().rejects(new Error('getProjectConfig failed'))
const getProjectOptions = cyPromptManagerSetupStub.args[0][0].getProjectOptions
const projectOptions = await getProjectOptions()
await expect(getProjectOptions()).to.be.rejectedWith('getProjectConfig failed')
expect(projectOptions.projectSlug).to.be.undefined
})
it('uses fallback projectId when getProjectConfig fails', async () => {
cyPromptLifecycleManager.initializeCyPromptManager({
cloudDataSource: mockCloudDataSource,
ctx: mockCtx,
record: false,
key: undefined,
projectId: 'fallback-project',
})
const cyPromptReadyPromise = new Promise((resolve) => {
cyPromptLifecycleManager?.registerCyPromptReadyListener(async (cyPromptManager) => {
resolve(cyPromptManager)
})
})
const mockManifest = {
'server/index.js': 'c3c4ab913ca059819549f105e756a4c4471df19abef884ce85eafc7b7970e7b4',
}
ensureCyPromptBundleStub.resolves(mockManifest)
await cyPromptReadyPromise
mockCtx.project.getConfig = sinon.stub().rejects(new Error('getProjectConfig failed'))
const getProjectOptions = cyPromptManagerSetupStub.args[0][0].getProjectOptions
const projectOptions = await getProjectOptions()
expect(projectOptions.projectSlug).to.equal('fallback-project')
})
it('only calls ensureCyPromptBundle once per cy prompt hash', async () => {
@@ -667,6 +699,23 @@ describe('CyPromptLifecycleManager', () => {
})
})
describe('resetCyPrompt', () => {
it('does nothing when cy prompt manager is not assigned', () => {
cyPromptLifecycleManager.resetCyPrompt()
})
it('calls reset on the manager when assigned', () => {
const resetStub = sinon.stub()
// @ts-expect-error - partial mock
cyPromptLifecycleManager.cyPromptManager = { reset: resetStub }
cyPromptLifecycleManager.resetCyPrompt()
expect(resetStub).to.be.calledOnce
})
})
describe('registerCyPromptReadyListener', () => {
beforeEach(() => {
const mockManifest = {
+2 -14
View File
@@ -522,9 +522,8 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
initializeCyPromptManagerStub.restore()
})
it('initializes cy prompt lifecycle manager if experimentalPromptCommand is enabled', function () {
it('initializes cy prompt lifecycle manager', function () {
this.config.projectId = 'abc123'
this.config.experimentalPromptCommand = true
this.project.options.record = true
this.project.options.key = '123e4567-e89b-12d3-a456-426614174000'
@@ -537,21 +536,10 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
ctx,
record: true,
key: '123e4567-e89b-12d3-a456-426614174000',
projectId: 'abc123',
})
})
})
it('does not initialize cy prompt lifecycle manager if experimentalPromptCommand is not enabled', function () {
this.config.projectId = 'abc123'
this.config.experimentalPromptCommand = false
initializeCyPromptManagerStub = sinon.stub(CyPromptLifecycleManager.prototype, 'initializeCyPromptManager')
return this.project.open()
.then(() => {
expect(initializeCyPromptManagerStub).not.to.be.called
})
})
})
describe('saved state', function () {
+5
View File
@@ -122,6 +122,9 @@ describe('lib/socket', () => {
getCyPrompt: sinon.stub().resolves({
cyPromptManager: mockCyPrompt,
}),
resetCyPrompt: sinon.stub().callsFake(() => {
mockCyPrompt.reset()
}),
registerCyPromptReadyListener: sinon.stub().callsFake((callback) => {
callback(mockCyPrompt)
@@ -472,6 +475,7 @@ describe('lib/socket', () => {
it('calls reset', async function () {
await new Promise((resolve) => {
this.client.emit('prompt:reset', () => {
expect(ctx.coreData.cyPromptLifecycleManager.resetCyPrompt).to.be.called
expect(mockCyPrompt.reset).to.be.called
resolve()
@@ -488,6 +492,7 @@ describe('lib/socket', () => {
await new Promise((resolve) => {
this.client.emit('prompt:reset', () => {
expect(ctx.coreData.cyPromptLifecycleManager.resetCyPrompt).not.to.be.called
expect(mockCyPrompt.reset).not.to.be.called
resolve()
-1
View File
@@ -31,7 +31,6 @@ export interface FullConfig extends Partial<Cypress.RuntimeConfigOptions & Cypre
export type ReceivedCypressOptions =
Pick<Cypress.RuntimeConfigOptions, 'hosts' | 'projectName' | 'clientRoute' | 'devServerPublicPathRoute' | 'namespace' | 'report' | 'socketIoCookie' | 'configFile' | 'isTextTerminal' | 'isNewProject' | 'proxyUrl' | 'browsers' | 'browserUrl' | 'socketIoRoute' | 'arch' | 'platform' | 'spec' | 'specs' | 'browser' | 'version' | 'remote'>
& Pick<Cypress.ResolvedConfigOptions, 'chromeWebSecurity' | 'supportFolder' | 'experimentalSourceRewriting' | 'fixturesFolder' | 'reporter' | 'reporterOptions' | 'screenshotsFolder' | 'supportFile' | 'baseUrl' | 'viewportHeight' | 'viewportWidth' | 'port' | 'experimentalInteractiveRunEvents' | 'userAgent' | 'downloadsFolder' | 'env' | 'excludeSpecPattern' | 'specPattern' | 'experimentalModifyObstructiveThirdPartyCode' | 'injectDocumentDomain' | 'video' | 'videoCompression' | 'videosFolder' | 'resolvedNodeVersion' | 'resolvedNodePath' | 'trashAssetsBeforeRuns' | 'experimentalWebKitSupport' | 'justInTimeCompile'>
& Pick<Cypress.ResolvedConfigOptions['e2e'], 'experimentalPromptCommand'> // TODO: Figure out how to type this better.
export interface SettingsOptions {
testingType?: 'component' |'e2e'
+1
View File
@@ -16,4 +16,5 @@ export interface CyPromptLifecycleManagerShape {
error?: Error
}>
registerCyPromptReadyListener: (listener: (cyPromptManager: CyPromptManagerShape) => void) => void
resetCyPrompt: () => void
}
@@ -1,70 +0,0 @@
exports['e2e prompt / fails when experimentalPromptCommand is not set'] = `
====================================================================================================
(Run Starting)
Cypress: 1.2.3
Browser: FooBrowser 88
Specs: 1 found (prompt.cy.js)
Searched: cypress/e2e/prompt.cy.js
Running: prompt.cy.js (1 of 1)
prompt
1) should fail when experimentalPromptCommand is not set
0 passing
1 failing
1) prompt
should fail when experimentalPromptCommand is not set:
PromptNotEnabledError: \`cy.prompt\` cannot be called without the \`experimentalPromptCommand\` being set.
Please set this in your Cypress config file to continue using \`cy.prompt\`.
[stack trace lines]
(Results)
Tests: 1
Passing: 0
Failing: 1
Pending: 0
Skipped: 0
Screenshots: 1
Video: false
Duration: X seconds
Spec Ran: prompt.cy.js
(Screenshots)
- /XXX/XXX/XXX/cypress/screenshots/prompt.cy.js/prompt -- should fail when experim (1280x720)
entalPromptCommand is not set (failed).png
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
prompt.cy.js XX:XX 1 - 1 - -
1 of 1 failed (100%) XX:XX 1 - 1 - -
`
@@ -30,7 +30,6 @@ exports['module api and after:run results'] = `
"experimentalModifyObstructiveThirdPartyCode": false,
"injectDocumentDomain": false,
"experimentalOriginDependencies": false,
"experimentalPromptCommand": false,
"experimentalSourceRewriting": false,
"experimentalSingleTabRunMode": false,
"experimentalWebKitSupport": false,
@@ -1,8 +0,0 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
allowCypressEnv: false,
supportFile: false,
},
})
@@ -1,7 +0,0 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// This property is invalid as `experimentalPromptCommand` is only available for e2e
experimentalPromptCommand: true,
e2e: {},
})
@@ -1,17 +0,0 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
// This property is invalid as `experimentalPromptCommand` is only available for e2e
experimentalPromptCommand: true,
devServer () {
// This test doesn't need to actually run any component tests
// so we create a fake dev server to make it run faster and
// avoid flake on CI.
return {
port: 1234,
close: () => {},
}
},
},
})
@@ -1,5 +0,0 @@
describe('prompt', () => {
it('should fail when experimentalPromptCommand is not set', () => {
cy.prompt(['Click the "click me" button'])
})
})
-14
View File
@@ -1,14 +0,0 @@
import systemTests from '../lib/system-tests'
describe('e2e prompt', () => {
systemTests.setup()
systemTests.it('fails when experimentalPromptCommand is not set', {
browser: 'electron',
project: 'experimentalPromptCommand',
spec: 'prompt.cy.js',
configFile: 'cypress-disabled-prompt-experiment.config.js',
expectedExitCode: 1,
snapshot: true,
})
})