Skip to content

Commit 7971d4c

Browse files
authored
Merge branch 'develop' into issue-33162
2 parents e667746 + 14f7a5e commit 7971d4c

39 files changed

+741
-208
lines changed

cli/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ _Released 01/27/2026 (PENDING)_
2626
- Upgraded `shell-env` to `4.0.1` and `@cypress/commit-info` to `2.2.2`. This removes the [GMS-2020-2](https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/blob/master/npm/execa/GMS-2020-2.yml) vulnerability being reported in security scans. Addressed in [#33226](https://github.com/cypress-io/cypress/pull/33226) and [#33263](https://github.com/cypress-io/cypress/pull/33263).
2727
- Upgraded `lodash` to `4.17.23`. This removes the [CVE-2025-13465](https://github.com/advisories/GHSA-xxjr-mmjv-4gpg) vulnerability being reported in security scans. Addresses [#33269](https://github.com/cypress-io/cypress/issues/33269).
2828

29+
**Bugfixes:**
30+
31+
- Fixed an issue where the user did not always have the ability to create a new test in Studio. Also, fixed an issue where creating a new test from an empty spec would display the welcome to studio screen instead of the form to name the new test. Addressed in [#33236](https://github.com/cypress-io/cypress/pull/33236).
2932

3033
## 15.9.0
3134

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@
270270
"@definitelytyped/typescript-versions": "0.1.9",
271271
"@types/react": "18.3.12",
272272
"browserify-sign": "4.2.5",
273-
"devtools-protocol": "0.0.1568893",
273+
"devtools-protocol": "0.0.1573491",
274274
"node-abi": "4.9.0",
275275
"vue-template-compiler": "2.6.12",
276276
"yargs-unparser": "1.6.4"

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ export function loadProjectAndRunSpec ({ projectName = 'experimental-studio' as
1414
cy.waitForSpecToFinish()
1515
}
1616

17+
export function openNewTestFromSpecHeader () {
18+
cy.get('[data-cy="runnable-options-button"]').click()
19+
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
20+
cy.get('[data-cy="runnable-popover-new-test"]').click()
21+
}
22+
1723
export function launchStudio ({ specName = 'spec.cy.js', createNewTestFromSuite = false, createNewTestFromSpecHeader = false, cliArgs = [''] } = {}) {
1824
loadProjectAndRunSpec({ specName, cliArgs })
1925

@@ -30,9 +36,7 @@ export function launchStudio ({ specName = 'spec.cy.js', createNewTestFromSuite
3036

3137
if (createNewTestFromSuite || createNewTestFromSpecHeader) {
3238
if (createNewTestFromSpecHeader) {
33-
cy.get('[data-cy="runnable-options-button"]').click()
34-
cy.get('[data-cy="more-options-runnable-popover"]').should('be.visible')
35-
cy.get('[data-cy="runnable-popover-new-test"]').click()
39+
openNewTestFromSpecHeader()
3640
} else {
3741
cy.get('@runnable-wrapper').realHover()
3842
cy.findByTestId('create-new-test-from-suite').click()
@@ -55,12 +59,7 @@ export function launchStudio ({ specName = 'spec.cy.js', createNewTestFromSuite
5559
}
5660
}
5761

58-
export function inputNewTestName ({ name = 'new-test', creatingNewTestFromWelcomeScreen = true }: { name?: string, creatingNewTestFromWelcomeScreen?: boolean } = {}) {
59-
if (creatingNewTestFromWelcomeScreen) {
60-
// we only need to click the new test button if we are not creating a new test from a suite or spec header
61-
cy.findByTestId('new-test-button').click()
62-
}
63-
62+
export function inputNewTestName ({ name = 'new-test' }: { name?: string } = {}) {
6463
cy.findByTestId('test-name-input').type(name)
6564
cy.findByTestId('create-test-button').click()
6665

packages/app/cypress/e2e/studio/studio-navigation.cy.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { launchStudio, loadProjectAndRunSpec, incrementCounter, inputNewTestName } from './helper'
1+
import { launchStudio, loadProjectAndRunSpec, incrementCounter, inputNewTestName, openNewTestFromSpecHeader } from './helper'
22

33
describe('Cypress Studio - Navigation and URL Management', () => {
44
it('does not re-enter studio mode when changing pages and then coming back', () => {
@@ -43,7 +43,7 @@ describe('Cypress Studio - Navigation and URL Management', () => {
4343
it('updates the AUT url when creating a new test', () => {
4444
launchStudio({ specName: 'navigation.cy.js', createNewTestFromSuite: true })
4545

46-
inputNewTestName({ creatingNewTestFromWelcomeScreen: false })
46+
inputNewTestName()
4747

4848
cy.findByTestId('aut-url-input').should('have.focus').type('cypress/e2e/navigation.html{enter}')
4949

@@ -73,26 +73,43 @@ describe('Cypress Studio - Navigation and URL Management', () => {
7373
it('update the url with the suiteId and studio parameters when entering studio with a suite', () => {
7474
launchStudio({ createNewTestFromSuite: true })
7575

76-
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=').and('contain', 'sessionId=')
76+
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=').and('contain', 'sessionId=').and('contain', 'entrySource=new-test-suite')
77+
})
78+
79+
it('removes entrySource parameter when going to a different page', () => {
80+
launchStudio({ createNewTestFromSuite: true })
81+
82+
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=').and('contain', 'entrySource=new-test-suite')
83+
84+
// go to the runs page
85+
cy.findByTestId('sidebar-link-runs-page').click()
86+
87+
cy.location().its('hash').should('contain', '/runs').and('not.contain', 'testId=').and('not.contain', 'studio=').and('not.contain', 'entrySource=')
7788
})
7889

79-
// TODO: unskip with https://github.com/cypress-io/cypress/pull/33236
80-
it.skip('updates the studio url parameters and displays the single test view after creating a new test', () => {
90+
it('updates the studio url parameters and displays the single test view after creating a new test', () => {
8191
loadProjectAndRunSpec()
8292

83-
// open the studio panel to create a new test in the root suite
84-
cy.findByTestId('studio-button').click()
85-
cy.location().its('hash').should('contain', 'suiteId=r1').and('contain', 'studio=').and('contain', 'sessionId=')
93+
// open the spec header to create a new test in the root suite
94+
openNewTestFromSpecHeader()
95+
cy.location().its('hash').should('contain', 'suiteId=r1').and('contain', 'studio=').and('contain', 'sessionId=').and('contain', 'entrySource=new-test-root')
8696

8797
// create a new test in the root suite
8898
inputNewTestName()
8999

90100
// the studio url parameters should be removed
91-
cy.location().its('hash').and('not.contain', 'suiteId=').and('contain', 'studio=').and('contain', 'testId=r2')
101+
cy.location().its('hash').and('not.contain', 'suiteId=').and('contain', 'studio=').and('contain', 'testId=r2').and('not.contain', 'entrySource=')
92102

93103
cy.get('.studio-single-test-container').should('be.visible')
94104

95105
cy.percySnapshot()
106+
107+
// after reloading, it should still display the single test view
108+
cy.reload()
109+
110+
// the studio url parameters should be removed
111+
cy.location().its('hash').and('not.contain', 'suiteId=').and('contain', 'studio=').and('contain', 'testId=r2').and('not.contain', 'entrySource=')
112+
cy.get('.studio-single-test-container').should('be.visible')
96113
})
97114

98115
it('does not remove the studio url parameters when saving test changes', () => {
@@ -188,10 +205,10 @@ describe('studio functionality', () => {
188205
it('removes the studio url parameters when closing studio new test', () => {
189206
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSuite: true })
190207

191-
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=')
208+
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=').and('contain', 'entrySource=new-test-suite')
192209

193210
cy.findByTestId('studio-header-studio-button').click()
194211

195-
cy.location().its('hash').and('not.contain', 'suiteId=').and('not.contain', 'studio=')
212+
cy.location().its('hash').and('not.contain', 'suiteId=').and('not.contain', 'studio=').and('not.contain', 'entrySource=')
196213
})
197214
})

packages/app/cypress/e2e/studio/studio-new-tests.cy.ts

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { launchStudio, loadProjectAndRunSpec, incrementCounter, inputNewTestName } from './helper'
1+
import { launchStudio, loadProjectAndRunSpec, incrementCounter, inputNewTestName, openNewTestFromSpecHeader } from './helper'
22

33
describe('Cypress Studio - New Test Creation', () => {
44
it('does not enter single test mode when creating a new test', () => {
@@ -15,7 +15,7 @@ describe('Cypress Studio - New Test Creation', () => {
1515
it('creates a new test from spec header', () => {
1616
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSpecHeader: true })
1717

18-
inputNewTestName({ creatingNewTestFromWelcomeScreen: false })
18+
inputNewTestName()
1919

2020
cy.contains('new-test').click()
2121

@@ -128,7 +128,7 @@ describe('studio functionality', () => {
128128
it('creates a new test for a specific suite with the url already defined', () => {
129129
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSuite: true })
130130

131-
inputNewTestName({ creatingNewTestFromWelcomeScreen: false })
131+
inputNewTestName()
132132

133133
// make sure that the visit has run and we're recording studio commands
134134
cy.get('[data-cy="record-button-recording"]').should('be.visible')
@@ -160,10 +160,41 @@ describe('studio functionality', () => {
160160
})
161161
})
162162

163-
// TODO: unskip with https://github.com/cypress-io/cypress/pull/33236
164-
it.skip('creates a new test from an empty spec', () => {
163+
it('creates a new test from an empty spec', () => {
164+
loadProjectAndRunSpec({ specName: 'empty.cy.js', specSelector: 'title' })
165+
166+
cy.contains('Create test with Cypress Studio').click()
167+
168+
inputNewTestName()
169+
170+
// Cypress re-runs after the new test is saved.
171+
cy.waitForSpecToFinish()
172+
173+
cy.get('.cm-content').invoke('text', 'cy.visit("cypress/e2e/index.html")')
174+
175+
cy.findByTestId('studio-save-button').click()
176+
177+
// verify recording is enabled to ensure the panel is fully ready
178+
cy.findByTestId('record-button-recording').should('have.text', 'Recording...')
179+
180+
// we should have the commands we executed after we save
181+
cy.withCtx(async (ctx) => {
182+
const spec = await ctx.actions.file.readFileInProject('cypress/e2e/empty.cy.js')
183+
184+
expect(spec.trim().replace(/\r/g, '')).to.equal(`
185+
it('new-test', function() {
186+
cy.visit("cypress/e2e/index.html")
187+
});`.trim())
188+
})
189+
})
190+
191+
it('opens the name test screen from the welcome screen and creates a new test from an empty spec', () => {
165192
loadProjectAndRunSpec({ specName: 'empty.cy.js', specSelector: 'title' })
166193

194+
cy.findByTestId('studio-button').click()
195+
196+
cy.get('[data-cy="studio-panel"]').should('be.visible')
197+
167198
cy.contains('Create test with Cypress Studio').click()
168199

169200
inputNewTestName()
@@ -207,17 +238,15 @@ it('new-test', function() {
207238
cy.findByTestId('studio-single-test-title').should('have.text', 'should be the only test to run normally')
208239
})
209240

210-
// TODO: unskip with https://github.com/cypress-io/cypress/pull/33236
211-
it.skip('creates and runs new tests in studio mode when there is a .only test in the spec file', () => {
241+
it('creates and runs new tests in studio mode when there is a .only test in the spec file', () => {
212242
loadProjectAndRunSpec({ specName: 'spec-with-only.cy.js' })
213243

214244
cy.get('.test').should('have.length', 1)
215245
cy.get('.test').contains('should be the only test to run normally').should('be.visible')
246+
// create a new test from the spec header
247+
openNewTestFromSpecHeader()
216248

217-
// launch studio and create a new test
218-
cy.findByTestId('studio-button').click()
219249
cy.findByTestId('studio-panel').should('be.visible').within(() => {
220-
cy.contains('button', 'New test').click()
221250
cy.get('[data-cy="test-name-input"]').type('new test{enter}')
222251
})
223252

@@ -226,6 +255,49 @@ it('new-test', function() {
226255
cy.get('[data-cy="studio-single-test-title"]').should('have.text', 'new test')
227256
})
228257

258+
it('continues to display the welcome screen after reloading', () => {
259+
loadProjectAndRunSpec({ specName: 'spec-w-visit.cy.js' })
260+
261+
// open welcome screen
262+
cy.findByTestId('studio-button').click()
263+
264+
cy.findByTestId('new-test-features').should('be.visible')
265+
cy.location().its('hash').should('contain', 'suiteId=r1').and('contain', 'studio=').and('contain', 'entrySource=welcome')
266+
267+
cy.reload()
268+
269+
cy.findByTestId('new-test-features').should('be.visible')
270+
cy.location().its('hash').should('contain', 'suiteId=r1').and('contain', 'studio=').and('contain', 'entrySource=welcome')
271+
})
272+
273+
it('continues to display the name test screen after reloading the page for a new test from root suite', () => {
274+
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSpecHeader: true })
275+
276+
cy.location().its('hash').should('contain', 'suiteId=r1').and('contain', 'studio=').and('contain', 'entrySource=new-test-root')
277+
278+
cy.get('[data-cy="name-test-container"]').should('be.visible')
279+
280+
cy.reload()
281+
282+
cy.location().its('hash').should('contain', 'suiteId=r1').and('contain', 'studio=').and('contain', 'entrySource=new-test-root')
283+
284+
cy.get('[data-cy="name-test-container"]').should('be.visible')
285+
})
286+
287+
it('continues to display the name test screen after reloading the page for a new test from suite', () => {
288+
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSuite: true })
289+
290+
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=').and('contain', 'entrySource=new-test-suite')
291+
292+
cy.get('[data-cy="name-test-container"]').should('be.visible')
293+
294+
cy.reload()
295+
296+
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=').and('contain', 'entrySource=new-test-suite')
297+
298+
cy.get('[data-cy="name-test-container"]').should('be.visible')
299+
})
300+
229301
describe('prompt for a new url', () => {
230302
const urlPrompt = '// Visit a page by entering a url in the address bar or typing a cy.visit command here'
231303
const autUrl = 'http://localhost:4455/cypress/e2e/index.html'

packages/app/cypress/e2e/studio/studio-ui.cy.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,13 @@ describe('studio functionality', () => {
3535
})
3636
})
3737

38-
// TODO: unskip with https://github.com/cypress-io/cypress/pull/33236
39-
it.skip('opens studio panel to new test when clicking on studio button (from the app) next to url', () => {
38+
it('opens studio panel to welcome screen when clicking on studio button (from the app) next to url', () => {
4039
cy.viewport(1500, 1000)
4140
loadProjectAndRunSpec()
4241
// studio button should be visible when using cloud studio
4342
cy.findByTestId('studio-button').should('be.visible').click()
4443
cy.findByTestId('studio-panel').should('be.visible')
45-
46-
cy.contains('New test')
44+
cy.findByTestId('new-test-features').should('be.visible')
4745

4846
cy.percySnapshot()
4947
})
@@ -85,22 +83,21 @@ describe('studio functionality', () => {
8583
cy.percySnapshot()
8684
})
8785

88-
// TODO: unskip with https://github.com/cypress-io/cypress/pull/33236
89-
it.skip('shows test body sections correctly when studio panel is open and page is refreshed', () => {
86+
it('shows test body sections correctly when studio panel is open and page is refreshed', () => {
9087
loadProjectAndRunSpec()
9188

9289
cy.waitForSpecToFinish()
9390

9491
cy.findByTestId('studio-button').click()
9592
cy.findByTestId('studio-panel').should('be.visible')
96-
cy.findByTestId('new-test-button').should('be.visible')
93+
cy.findByTestId('new-test-features').should('be.visible')
9794

9895
cy.reload()
9996

10097
cy.waitForSpecToFinish()
10198

10299
cy.findByTestId('studio-panel').should('be.visible')
103-
cy.findByTestId('new-test-button').should('be.visible')
100+
cy.findByTestId('new-test-features').should('be.visible')
104101

105102
// verify test body section is visible after refresh
106103
cy.get('.runnable-instruments').should('be.visible')
@@ -116,15 +113,14 @@ describe('studio functionality', () => {
116113
cy.location().its('hash').should('contain', 'suiteId=r1').and('not.contain', 'testId=')
117114
})
118115

119-
// TODO: unskip with https://github.com/cypress-io/cypress/pull/33236
120-
it.skip('stays in new test mode when studio panel is opened when the spec is running', () => {
116+
it('stays in the welcome screen when studio panel is opened while the spec is running', () => {
121117
loadProjectAndRunSpec()
122118

123119
cy.waitForSpecToFinish()
124120

125121
cy.findByTestId('studio-button').click()
126122
cy.findByTestId('studio-panel').should('be.visible')
127-
cy.findByTestId('new-test-button').should('be.visible')
123+
cy.findByTestId('new-test-features').should('be.visible')
128124

129125
// Verify we're initially in new test mode
130126
cy.location().its('hash').should('contain', 'suiteId=r1').and('not.contain', 'testId=')
@@ -139,7 +135,7 @@ describe('studio functionality', () => {
139135

140136
// verify we're still in new test mode
141137
cy.findByTestId('studio-panel').should('be.visible')
142-
cy.findByTestId('new-test-button').should('be.visible')
138+
cy.findByTestId('new-test-features').should('be.visible')
143139

144140
// these should not exist if we stayed in new test mode
145141
cy.findByTestId('studio-single-test-title').should('not.exist')

packages/app/src/runner/ResizablePanels.cy.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,4 +331,16 @@ describe('<ResizablePanels />', { viewportWidth: 1500 }, () => {
331331
assertWidth('panel3', 1160)
332332
})
333333
})
334+
335+
describe('panel attributes', () => {
336+
it('should have the correct aut-panel identifiers', () => {
337+
cy.mount(() => (
338+
<div class="h-screen">
339+
<ResizablePanels maxTotalWidth={1500} v-slots={slotContents} />
340+
</div>
341+
))
342+
343+
cy.get('[data-cy="aut-panel"]').should('have.id', 'aut-panel')
344+
})
345+
})
334346
})

packages/app/src/runner/ResizablePanels.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
</div>
4545

4646
<div
47+
id="aut-panel"
4748
data-cy="aut-panel"
4849
class="grow h-full bg-gray-100 relative"
4950
:class="{ 'pointer-events-none': panel2IsDragging || panel4IsDragging }"

packages/app/src/runner/SpecRunnerOpenMode.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
:has-requested-project-access="hasRequestedProjectAccess"
113113
:request-project-access-mutation="requestProjectAccessMutation"
114114
:spec-dirty-data-store="specDirtyDataStore"
115+
:aut-snapshot-store="snapshotStore"
115116
/>
116117
</HideDuringScreenshot>
117118
</template>
@@ -155,6 +156,7 @@ import PromptMoreInfoNeededModal from '../prompt/PromptMoreInfoNeededModal.vue'
155156
import { usePromptStore } from '../store/prompt-store'
156157
import { useUserProjectStatusStore } from '@packages/frontend-shared/src/store/user-project-status-store'
157158
import { useSpecDirtyDataStore } from '../store/spec-dirty-data-store'
159+
import { useSnapshotStore } from './snapshot-store'
158160
159161
// this is used by the StudioPanel to access the AUT URL input
160162
const autUrlSelector = '.aut-url-input'
@@ -170,6 +172,7 @@ const {
170172
171173
const userProjectStatusStore = useUserProjectStatusStore()
172174
const specDirtyDataStore = useSpecDirtyDataStore()
175+
const snapshotStore = useSnapshotStore()
173176
174177
gql`
175178
fragment SpecRunner_Preferences on Query {

0 commit comments

Comments
 (0)