Skip to content

Commit 3bd6eac

Browse files
merge develop into 15
2 parents 10c6656 + bd2f90d commit 3bd6eac

37 files changed

+1202
-1010
lines changed

.circleci/cache-version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Bump this version to force CI to re-create the cache from scratch.
22

3-
4-28-2025
3+
4-29-2025

.circleci/workflows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: 2.1
22

33
chrome-stable-version: &chrome-stable-version "136.0.7103.92"
4-
chrome-beta-version: &chrome-beta-version "137.0.7151.6"
4+
chrome-beta-version: &chrome-beta-version "137.0.7151.15"
55
firefox-stable-version: &firefox-stable-version "137.0"
66

77
orbs:

cli/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ _Released 07/01/2025 (PENDING)_
2222

2323
- Migration helpers and related errors are no longer shown when upgrading from Cypress versions earlier than 10.0.0. To migrate from a pre-10.0.0 version, upgrade one major version at a time to receive the appropriate guidance. Addresses [#31345](https://github.com/cypress-io/cypress/issues/31345). Addressed in [https://github.com/cypress-io/cypress/pull/31629/](https://github.com/cypress-io/cypress/pull/31629/).
2424

25+
## 14.3.4
26+
27+
_Released 5/20/2025 (PENDING)_
28+
29+
**Dependency Updates:**
30+
31+
- Upgraded `trash` from `5.2.0` to `7.2.0`. Addressed in [#31667](https://github.com/cypress-io/cypress/pull/31667).
32+
33+
2534
## 14.3.3
2635

2736
_Released 5/6/2025_

packages/app/cypress/e2e/studio/helper.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,31 @@ export function launchStudio ({ specName = 'spec.cy.js', createNewTest = false,
4343
cy.get('[data-cy="hook-name-studio commands"]').should('exist')
4444
}
4545
}
46+
47+
export function assertClosingPanelWithoutChanges () {
48+
// Cypress re-runs after you cancel Studio.
49+
// Original spec should pass
50+
cy.waitForSpecToFinish({ passCount: 1 })
51+
52+
cy.get('.command').should('have.length', 1)
53+
54+
// Assert the spec was executed without any new commands.
55+
cy.get('.command-name-visit').within(() => {
56+
cy.contains('visit')
57+
cy.contains('cypress/e2e/index.html')
58+
})
59+
60+
cy.findByTestId('hook-name-studio commands').should('not.exist')
61+
62+
cy.withCtx(async (ctx) => {
63+
const spec = await ctx.actions.file.readFileInProject('cypress/e2e/spec.cy.js')
64+
65+
// No change, since we closed studio
66+
expect(spec.trim().replace(/\r/g, '')).to.eq(`
67+
describe('studio functionality', () => {
68+
it('visits a basic html page', () => {
69+
cy.visit('cypress/e2e/index.html')
70+
})
71+
})`.trim())
72+
})
73+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { launchStudio, loadProjectAndRunSpec, assertClosingPanelWithoutChanges } from './helper'
2+
import pDefer from 'p-defer'
3+
4+
describe('Studio Cloud', () => {
5+
it('enables protocol for cloud studio', () => {
6+
launchStudio({ enableCloudStudio: true })
7+
8+
cy.window().then((win) => {
9+
expect(win.Cypress.config('isDefaultProtocolEnabled')).to.be.false
10+
expect(win.Cypress.state('isProtocolEnabled')).to.be.true
11+
})
12+
})
13+
14+
it('loads the studio UI correctly when studio bundle is taking too long to load', () => {
15+
loadProjectAndRunSpec({ enableCloudStudio: false })
16+
17+
cy.window().then(() => {
18+
cy.withCtx((ctx) => {
19+
// Mock the studioLifecycleManager.getStudio method to return a hanging promise
20+
if (ctx.coreData.studioLifecycleManager) {
21+
const neverResolvingPromise = new Promise<null>(() => {})
22+
23+
ctx.coreData.studioLifecycleManager.getStudio = () => neverResolvingPromise
24+
ctx.coreData.studioLifecycleManager.isStudioReady = () => false
25+
}
26+
})
27+
})
28+
29+
cy.contains('visits a basic html page')
30+
.closest('.runnable-wrapper')
31+
.findByTestId('launch-studio')
32+
.click()
33+
34+
cy.waitForSpecToFinish()
35+
36+
// Verify the cloud studio panel is not present
37+
cy.findByTestId('studio-panel').should('not.exist')
38+
39+
cy.get('[data-cy="loading-studio-panel"]').should('not.exist')
40+
41+
cy.get('[data-cy="hook-name-studio commands"]').should('exist')
42+
43+
cy.getAutIframe().within(() => {
44+
cy.get('#increment').realClick()
45+
})
46+
47+
cy.findByTestId('hook-name-studio commands').closest('.hook-studio').within(() => {
48+
cy.get('.command').should('have.length', 2)
49+
cy.get('.command-name-get').should('contain.text', '#increment')
50+
cy.get('.command-name-click').should('contain.text', 'click')
51+
})
52+
53+
cy.get('button').contains('Save Commands').should('not.be.disabled')
54+
})
55+
56+
it('immediately loads the studio panel', () => {
57+
const deferred = pDefer()
58+
59+
loadProjectAndRunSpec({ enableCloudStudio: true })
60+
61+
cy.findByTestId('studio-panel').should('not.exist')
62+
63+
cy.intercept('/cypress/e2e/index.html', () => {
64+
// wait for the promise to resolve before responding
65+
// this will ensure the studio panel is loaded before the test finishes
66+
return deferred.promise
67+
}).as('indexHtml')
68+
69+
cy.contains('visits a basic html page')
70+
.closest('.runnable-wrapper')
71+
.findByTestId('launch-studio')
72+
.click()
73+
74+
// regular studio is not loaded until after the test finishes
75+
cy.get('[data-cy="hook-name-studio commands"]').should('not.exist')
76+
// cloud studio is loaded immediately
77+
cy.findByTestId('studio-panel').then(() => {
78+
// check for the loading panel from the app first
79+
cy.get('[data-cy="loading-studio-panel"]').should('be.visible')
80+
// we've verified the studio panel is loaded, now resolve the promise so the test can finish
81+
deferred.resolve()
82+
})
83+
84+
cy.wait('@indexHtml')
85+
86+
// Studio re-executes spec before waiting for commands - wait for the spec to finish executing.
87+
cy.waitForSpecToFinish()
88+
89+
// Verify the studio panel is still open
90+
cy.findByTestId('studio-panel')
91+
cy.get('[data-cy="hook-name-studio commands"]')
92+
})
93+
94+
it('hides selector playground and studio controls when studio beta is available', () => {
95+
launchStudio({ enableCloudStudio: true })
96+
cy.get('[data-cy="studio-header-studio-button"]').click()
97+
98+
cy.get('[data-cy="playground-activator"]').should('not.exist')
99+
cy.get('[data-cy="studio-toolbar"]').should('not.exist')
100+
})
101+
102+
it('closes studio panel when clicking studio button (from the cloud)', () => {
103+
launchStudio({ enableCloudStudio: true })
104+
105+
cy.get('[data-cy="studio-header-studio-button"]').click()
106+
107+
assertClosingPanelWithoutChanges()
108+
})
109+
110+
it('opens studio panel to new test when clicking on studio button (from the app) next to url', () => {
111+
cy.viewport(1500, 1000)
112+
loadProjectAndRunSpec({ enableCloudStudio: true })
113+
// studio button should be visible when using cloud studio
114+
cy.get('[data-cy="studio-button"]').should('be.visible').click()
115+
cy.get('[data-cy="studio-panel"]').should('be.visible')
116+
117+
cy.contains('New Test')
118+
119+
cy.get('[data-cy="studio-url-prompt"]').should('not.exist')
120+
121+
cy.percySnapshot()
122+
})
123+
124+
it('opens a cloud studio session with AI enabled', () => {
125+
cy.mockNodeCloudRequest({
126+
url: '/studio/testgen/n69px6/enabled',
127+
method: 'get',
128+
body: { enabled: true },
129+
})
130+
131+
const aiOutput = 'cy.get(\'button\').should(\'have.text\', \'Increment\')'
132+
133+
cy.mockNodeCloudStreamingRequest({
134+
url: '/studio/testgen/n69px6/generate',
135+
method: 'post',
136+
body: { recommendations: [{ content: aiOutput }] },
137+
})
138+
139+
cy.mockStudioFullSnapshot({
140+
id: 1,
141+
nodeType: 1,
142+
nodeName: 'div',
143+
localName: 'div',
144+
nodeValue: 'div',
145+
children: [],
146+
shadowRoots: [],
147+
})
148+
149+
const deferred = pDefer()
150+
151+
loadProjectAndRunSpec({ enableCloudStudio: true })
152+
153+
cy.findByTestId('studio-panel').should('not.exist')
154+
155+
cy.intercept('/cypress/e2e/index.html', () => {
156+
// wait for the promise to resolve before responding
157+
// this will ensure the studio panel is loaded before the test finishes
158+
return deferred.promise
159+
}).as('indexHtml')
160+
161+
cy.contains('visits a basic html page')
162+
.closest('.runnable-wrapper')
163+
.findByTestId('launch-studio')
164+
.click()
165+
166+
// regular studio is not loaded until after the test finishes
167+
cy.get('[data-cy="hook-name-studio commands"]').should('not.exist')
168+
// cloud studio is loaded immediately
169+
cy.findByTestId('studio-panel').then(() => {
170+
// check for the loading panel from the app first
171+
cy.get('[data-cy="loading-studio-panel"]').should('be.visible')
172+
// we've verified the studio panel is loaded, now resolve the promise so the test can finish
173+
deferred.resolve()
174+
})
175+
176+
cy.wait('@indexHtml')
177+
178+
// Studio re-executes spec before waiting for commands - wait for the spec to finish executing.
179+
cy.waitForSpecToFinish()
180+
181+
// Verify the studio panel is still open
182+
cy.findByTestId('studio-panel')
183+
cy.get('[data-cy="hook-name-studio commands"]')
184+
185+
// Verify that AI is enabled
186+
cy.get('[data-cy="ai-status-text"]').should('contain.text', 'Enabled')
187+
188+
// Verify that the AI output is correct
189+
cy.get('[data-cy="recommendation-editor"]').should('contain', aiOutput)
190+
})
191+
})

0 commit comments

Comments
 (0)