mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-25 01:49:06 -05:00
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:
committed by
GitHub
parent
b21fa13daa
commit
1bf3ff8023
Vendored
-5
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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`:
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,6 @@ export const baseConfig: Cypress.ConfigOptions = {
|
||||
configFile: '../../mocha-reporter-config.json',
|
||||
},
|
||||
e2e: {
|
||||
experimentalPromptCommand: true,
|
||||
experimentalOriginDependencies: true,
|
||||
experimentalModifyObstructiveThirdPartyCode: true,
|
||||
setupNodeEvents: (on, config) => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 @@
|
||||
[31mThe [33mexperimentalPromptCommand[39m[31m experiment is currently only supported for End to End Testing and must be configured as an e2e testing type property: [95me2e.experimentalPromptCommand[39m[31m.[39m
|
||||
[31m[39m
|
||||
[31m[94m{[39m[31m[39m
|
||||
[31m[94m e2e: {[39m[31m[39m
|
||||
[31m[94m experimentalPromptCommand: true[39m[31m[39m
|
||||
[31m[94m },[39m[31m[39m
|
||||
[31m[94m}[39m[31m[39m
|
||||
@@ -0,0 +1,5 @@
|
||||
[31mThe [33mexperimentalPromptCommand[39m[31m option was removed in Cypress version 15.13.0.[39m
|
||||
[31m[39m
|
||||
[31m`cy.prompt` is now available for all users.[39m
|
||||
[31m[39m
|
||||
[31mYou can safely remove this option from your config.[39m
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
allowCypressEnv: false,
|
||||
supportFile: false,
|
||||
},
|
||||
})
|
||||
-7
@@ -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: {},
|
||||
})
|
||||
-17
@@ -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'])
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user