Skip to content

feat: prompt (WIP - do not merge) #31752

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dc7a054
Create cy-prompt-development.md
ryanthemanuel May 20, 2025
c26b182
Merge branch 'develop' into feat/cy-prompt
ryanthemanuel May 20, 2025
aa543ae
chore: cy prompt infrastructure (#31748)
ryanthemanuel May 28, 2025
ae8fb6a
Merge branch 'develop' into feat/cy-prompt
ryanthemanuel May 30, 2025
8785623
Merge branch 'develop' into feat/cy-prompt
ryanthemanuel Jun 2, 2025
18b0d9d
fix test
ryanthemanuel Jun 2, 2025
3bc98e0
Delete packages/server/lib/cloud/StudioLifecycleManager.ts
ryanthemanuel Jun 2, 2025
93430dc
Delete packages/server/test/unit/cloud/StudioLifecycleManager_spec.ts
ryanthemanuel Jun 2, 2025
0051c3d
Merge branch 'develop' into feat/cy-prompt
ryanthemanuel Jun 2, 2025
ebba6e4
chore: add cdp connection to cy prompt (#31806)
ryanthemanuel Jun 3, 2025
22737d2
chore: create infrastructure to support backend function in cy.prompt…
ryanthemanuel Jun 3, 2025
e18d31a
chore: add watcher for cy-prompt development (#31810)
ryanthemanuel Jun 3, 2025
832867d
chore: turn on beta deployments for cy-prompt
ryanthemanuel Jun 4, 2025
58e3234
internal: (cy.prompt) handle errors better in the command definition …
ryanthemanuel Jun 6, 2025
b4a663a
Merge branch 'develop' into feat/cy-prompt
ryanthemanuel Jun 9, 2025
30e48d1
chore: handle errors (#31854)
estrada9166 Jun 10, 2025
6c3b69e
Merge branch 'develop' into feat/cy-prompt
ryanthemanuel Jun 13, 2025
04e8212
chores: (cy.prompt) refactor routing to support both app and driver (…
ryanthemanuel Jun 13, 2025
33277c1
chore: Share error utils with the cloud (#31887)
estrada9166 Jun 13, 2025
9a417af
Merge branch 'develop' into feat/cy-prompt
ryanthemanuel Jun 18, 2025
2e4c8e4
internal: (cy.prompt) add infrastructure to support a Get Code modal …
ryanthemanuel Jun 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .circleci/workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mainBuildFilters: &mainBuildFilters
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- 'update-v8-snapshot-cache-on-develop'
- 'update-chrome-stable-from-136.0.7103.113-beta-from-137.0.7151.40'
- 'feat/cy-prompt'

# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
Expand All @@ -49,6 +50,7 @@ macWorkflowFilters: &darwin-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'feat/cy-prompt', << pipeline.git.branch >> ]
- equal:
[
'update-chrome-stable-from-136.0.7103.113-beta-from-137.0.7151.40',
Expand All @@ -64,6 +66,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'feat/cy-prompt', << pipeline.git.branch >> ]
- equal:
[
'update-chrome-stable-from-136.0.7103.113-beta-from-137.0.7151.40',
Expand Down Expand Up @@ -91,6 +94,7 @@ windowsWorkflowFilters: &windows-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'feat/cy-prompt', << pipeline.git.branch >> ]
- equal:
[
'update-chrome-stable-from-136.0.7103.113-beta-from-137.0.7151.40',
Expand Down Expand Up @@ -169,7 +173,7 @@ commands:
name: Set environment variable to determine whether or not to persist artifacts
command: |
echo "Setting SHOULD_PERSIST_ARTIFACTS variable"
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-chrome-stable-from-136.0.7103.113-beta-from-137.0.7151.40" ]]; then
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-chrome-stable-from-136.0.7103.113-beta-from-137.0.7151.40" && "$CIRCLE_BRANCH" != "feat/cy-prompt" ]]; then
export SHOULD_PERSIST_ARTIFACTS=true
fi' >> "$BASH_ENV"
# You must run `setup_should_persist_artifacts` command and be using bash before running this command
Expand Down
39 changes: 39 additions & 0 deletions guides/cy-prompt-development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# `cy.prompt` Development

In production, the code used to facilitate the prompt command will be retrieved from the Cloud. While `cy.prompt` is still in its early stages it is hidden behind an environment variable: `CYPRESS_ENABLE_CY_PROMPT` but can also be run against local cloud Studio code via the environment variable: `CYPRESS_LOCAL_CY_PROMPT_PATH`.

To run against locally developed `cy.prompt`:

- Clone the `cypress-services` repo
- Run `yarn`
- Run `yarn watch` in `app/packages/cy-prompt`
- Set:
- `CYPRESS_INTERNAL_ENV=<environment>` (e.g. `staging` or `production` if you want to hit those deployments of `cypress-services` or `development` if you want to hit a locally running version of `cypress-services`)
- `CYPRESS_LOCAL_CY_PROMPT_PATH` to the path to the `cypress-services/app/packages/cy-prompt/dist/development` directory

To run against a deployed version of `cy.prompt`:

- Set:
- `CYPRESS_INTERNAL_ENV=<environment>` (e.g. `staging` or `production` if you want to hit those deployments of `cypress-services` or `development` if you want to hit a locally running version of `cypress-services`)
- `CYPRESS_ENABLE_CY_PROMPT=true`

Regardless of running against local or deployed `cy.prompt`:

- Clone the `cypress` repo
- Run `yarn`
- Run `yarn cypress:open`
- Log In to the Cloud via the App
- Open a project that has `experimentalPromptCommand: true` set in the `e2e` config of the `cypress.config.js|ts` file.

To run against a deployed version of `cy.prompt`:

- Set:
- `CYPRESS_INTERNAL_ENV=<environment>` (e.g. `staging` or `production` if you want to hit those deployments of `cypress-services` or `development` if you want to hit a locally running version of `cypress-services`)

## Testing

### Unit/Component Testing

The code that supports cloud `cy.prompt` and lives in the `cypress` monorepo is unit, integration, and e2e tested in a similar fashion to the rest of the code in the repo. See the [contributing guide](https://github.com/cypress-io/cypress/blob/ad353fcc0f7fdc51b8e624a2a1ef4e76ef9400a0/CONTRIBUTING.md?plain=1#L366) for more specifics.

The code that supports cloud `cy.prompt` and lives in the `cypress-services` monorepo has unit tests that live alongside the code in that monorepo.
16 changes: 1 addition & 15 deletions packages/app/src/runner/event-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -798,21 +798,7 @@ export class EventManager {
},
)

/**
* Call a backend request for the requesting spec bridge since we cannot have websockets in the spec bridges.
* Return it's response.
*/
Cypress.primaryOriginCommunicator.on('backend:request', async ({ args }, { source, responseEvent }) => {
let response

try {
response = await Cypress.backend(...args)
} catch (error) {
response = { error }
}

Cypress.primaryOriginCommunicator.toSource(source, responseEvent, response)
})
Cypress.handlePrimaryOriginSocketEvent(Cypress, 'backend:request')

/**
* Call an automation request for the requesting spec bridge since we cannot have websockets in the spec bridges.
Expand Down
3 changes: 3 additions & 0 deletions packages/config/__snapshots__/index.spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
'experimentalSourceRewriting': false,
'experimentalSingleTabRunMode': false,
'experimentalStudio': false,
'experimentalPromptCommand': false,
'experimentalWebKitSupport': false,
'fileServerFolder': '',
'fixturesFolder': 'cypress/fixtures',
Expand Down Expand Up @@ -137,6 +138,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
'experimentalSourceRewriting': false,
'experimentalSingleTabRunMode': false,
'experimentalStudio': false,
'experimentalPromptCommand': false,
'experimentalWebKitSupport': false,
'fileServerFolder': '',
'fixturesFolder': 'cypress/fixtures',
Expand Down Expand Up @@ -224,6 +226,7 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
'experimentalSourceRewriting',
'experimentalSingleTabRunMode',
'experimentalStudio',
'experimentalPromptCommand',
'experimentalWebKitSupport',
'fileServerFolder',
'fixturesFolder',
Expand Down
6 changes: 6 additions & 0 deletions packages/config/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,12 @@ const driverConfigOptions: Array<DriverConfigOption> = [
validation: validate.isBoolean,
isExperimental: true,
requireRestartOnChange: 'browser',
}, {
name: 'experimentalPromptCommand',
defaultValue: false,
validation: validate.isBoolean,
isExperimental: true,
requireRestartOnChange: 'server',
}, {
name: 'experimentalWebKitSupport',
defaultValue: false,
Expand Down
2 changes: 2 additions & 0 deletions packages/config/test/project/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,7 @@ describe('config/src/project/utils', () => {
experimentalRunAllSpecs: { value: false, from: 'default' },
experimentalSingleTabRunMode: { value: false, from: 'default' },
experimentalStudio: { value: false, from: 'default' },
experimentalPromptCommand: { value: false, from: 'default' },
experimentalSourceRewriting: { value: false, from: 'default' },
experimentalWebKitSupport: { value: false, from: 'default' },
fileServerFolder: { value: '', from: 'default' },
Expand Down Expand Up @@ -1197,6 +1198,7 @@ describe('config/src/project/utils', () => {
experimentalRunAllSpecs: { value: false, from: 'default' },
experimentalSingleTabRunMode: { value: false, from: 'default' },
experimentalStudio: { value: false, from: 'default' },
experimentalPromptCommand: { value: false, from: 'default' },
experimentalSourceRewriting: { value: false, from: 'default' },
experimentalWebKitSupport: { value: false, from: 'default' },
fileServerFolder: { value: '', from: 'default' },
Expand Down
3 changes: 2 additions & 1 deletion packages/data-context/src/data/coreDataShape.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FoundBrowser, Editor, AllowedState, AllModeOptions, TestingType, BrowserStatus, PACKAGE_MANAGERS, AuthStateName, MIGRATION_STEPS, MigrationStep, StudioLifecycleManagerShape } from '@packages/types'
import { FoundBrowser, Editor, AllowedState, AllModeOptions, TestingType, BrowserStatus, PACKAGE_MANAGERS, AuthStateName, MIGRATION_STEPS, MigrationStep, StudioLifecycleManagerShape, CyPromptLifecycleManagerShape } from '@packages/types'
import { WizardBundler, CT_FRAMEWORKS, resolveComponentFrameworkDefinition, ErroredFramework } from '@packages/scaffold-config'
import type { NexusGenObjects } from '@packages/graphql/src/gen/nxs.gen'
// tslint:disable-next-line no-implicit-dependencies - electron dep needs to be defined
Expand Down Expand Up @@ -165,6 +165,7 @@ export interface CoreDataShape {
eventCollectorSource: EventCollectorSource | null
didBrowserPreviouslyHaveUnexpectedExit: boolean
studioLifecycleManager?: StudioLifecycleManagerShape
cyPromptLifecycleManager?: CyPromptLifecycleManagerShape
}

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/driver/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const baseConfig: Cypress.ConfigOptions = {
experimentalStudio: true,
experimentalMemoryManagement: true,
experimentalWebKitSupport: true,
// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
experimentalPromptCommand: true,
hosts: {
'foobar.com': '127.0.0.1',
'*.foobar.com': '127.0.0.1',
Expand Down Expand Up @@ -38,7 +40,7 @@ export const baseConfig: Cypress.ConfigOptions = {
},
component: {
experimentalSingleTabRunMode: true,
specPattern: 'cypress/component/**/*.cy.js',
specPattern: 'cypress/component/**/*.cy.{js,ts}',
supportFile: false,
devServer: (devServerOptions) => {
return cypressWebpackDevServer({
Expand Down
13 changes: 13 additions & 0 deletions packages/driver/cypress/component/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,17 @@ describe('component testing', () => {
expect(Cypress.log).to.be.calledWithMatch(sinon.match({ 'message': `Error: "Promise rejected with a string!"`, name: 'uncaught exception' }))
})
})

it('fails when trying to use cy.prompt in component tests', (done) => {
cy.spy(Cypress, 'log').log(false)

cy.on('fail', (err) => {
expect(err.message).to.include('`cy.prompt` is currently only supported in end-to-end tests.')

done()
})

// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
cy.prompt('Hello, world!')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
describe('src/cy/commands/prompt', () => {
it('errors if wait for ready does not return success and error is ENOSPC', (done) => {
const backendStub = cy.stub(Cypress, 'backend').log(false)

const error = new Error(`no space left on device, open '<stripped-path>bundle.tar`)

error.name = 'ENOSPC'

backendStub.callThrough()
backendStub.withArgs('wait:for:cy:prompt:ready').resolves({ success: false, error })

cy.on('fail', (err) => {
expect(err.message).to.include('Failed to download cy.prompt Cloud code')
expect(err.message).to.include(`no space left on device, open '<stripped-path>bundle.tar`)

done()
})

cy.visit('http://www.foobar.com:3500/fixtures/dom.html')

cy['commandFns']['prompt'].__reset()
// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
cy.prompt('Hello, world!')
})

it('errors if wait for ready does not return success and error is ECONNREFUSED', (done) => {
const backendStub = cy.stub(Cypress, 'backend').log(false)

const error = new Error(`'<stripped-path>bundle.tar' timed out after 10000s`)

error.name = 'ECONNREFUSED'

backendStub.callThrough()
backendStub.withArgs('wait:for:cy:prompt:ready').resolves({ success: false, error })

cy.on('fail', (err) => {
expect(err.message).to.include('Timed out waiting for cy.prompt Cloud code:')
expect(err.message).to.include(`'<stripped-path>bundle.tar' timed out after 10000s`)

done()
})

cy.visit('http://www.foobar.com:3500/fixtures/dom.html')

cy['commandFns']['prompt'].__reset()
// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
cy.prompt('Hello, world!')
})
})
42 changes: 42 additions & 0 deletions packages/driver/cypress/e2e/commands/prompt/prompt.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
describe('src/cy/commands/prompt', () => {
it('executes the prompt command', () => {
// TODO: (cy.prompt) We will look into supporting other browsers
// as this is rolled out. We will add error messages for other browsers
// and add tests if necessary
if (Cypress.isBrowser('webkit') || Cypress.isBrowser('firefox')) {
return
}

cy.visit('http://www.foobar.com:3500/fixtures/dom.html')

// TODO: add more tests when cy.prompt is built out, but for now this just
// verifies that the command executes without throwing an error
// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
cy.prompt('Hello, world!')

cy.visit('http://www.barbaz.com:3500/fixtures/dom.html')

cy.origin('http://www.barbaz.com:3500', () => {
// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
cy.prompt('Hello, world!')
})
})

it('fails when trying to use cy.prompt in a browser that is not supported', (done) => {
if (Cypress.isBrowser({ family: 'chromium' })) {
done()

return
}

cy.on('fail', (err) => {
expect(err.message).to.include('`cy.prompt` is only supported in Chromium-based browsers.')

done()
})

cy.visit('http://www.foobar.com:3500/fixtures/dom.html')
// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
cy.prompt('Hello, world!')
})
})
2 changes: 1 addition & 1 deletion packages/driver/cypress/e2e/e2e/origin/commands/misc.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ it('verifies number of cy commands', () => {
'writeFile', 'fixture', 'clearLocalStorage', 'url', 'hash', 'location', 'end', 'noop', 'log', 'wrap', 'reload', 'go', 'visit',
'focused', 'get', 'contains', 'shadow', 'within', 'request', 'session', 'screenshot', 'task', 'find', 'filter', 'not',
'children', 'eq', 'closest', 'first', 'last', 'next', 'nextAll', 'nextUntil', 'parent', 'parents', 'parentsUntil', 'prev', 'press',
'prevAll', 'prevUntil', 'siblings', 'wait', 'title', 'window', 'document', 'viewport', 'server', 'route', 'intercept', 'origin',
'prevAll', 'prevUntil', 'prompt', 'siblings', 'wait', 'title', 'window', 'document', 'viewport', 'server', 'route', 'intercept', 'origin',
'mount', 'as', 'root', 'getAllLocalStorage', 'clearAllLocalStorage', 'getAllSessionStorage', 'clearAllSessionStorage',
'getAllCookies', 'clearAllCookies',
]
Expand Down
Loading
Loading