Skip to content

Commit ced047b

Browse files
internal: add create new test button in spec header (#32141)
* refactor create new test button into its own component * add create new test button component to spec file name * add test for create new test button in the spec header * pass a dataCy for create new test button * fix failing tests * Update packages/app/cypress/e2e/studio/helper.ts Co-authored-by: Jennifer Shehane <[email protected]> * revert changes to the if/else logic * fix failing test * fix flaky test * update create new spec from root test --------- Co-authored-by: Jennifer Shehane <[email protected]>
1 parent d18f660 commit ced047b

File tree

8 files changed

+89
-62
lines changed

8 files changed

+89
-62
lines changed

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

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

17-
export function launchStudio ({ specName = 'spec.cy.js', createNewTest = false, cliArgs = [''] } = {}) {
17+
export function launchStudio ({ specName = 'spec.cy.js', createNewTestFromSuite = false, createNewTestFromSpecHeader = false, cliArgs = [''] } = {}) {
1818
loadProjectAndRunSpec({ specName, cliArgs })
1919

2020
const testTitle = 'visits a basic html page'
2121

22-
if (createNewTest) {
22+
if (createNewTestFromSuite || createNewTestFromSpecHeader) {
2323
cy.contains('studio functionality').as('item')
2424
} else {
2525
cy.contains(testTitle).as('item')
@@ -28,8 +28,13 @@ export function launchStudio ({ specName = 'spec.cy.js', createNewTest = false,
2828
cy.get('@item')
2929
.closest('.runnable-wrapper').as('runnable-wrapper')
3030

31-
if (createNewTest) {
32-
cy.get('@runnable-wrapper').realHover().findByTestId('create-new-test-button').click()
31+
if (createNewTestFromSuite || createNewTestFromSpecHeader) {
32+
if (createNewTestFromSpecHeader) {
33+
cy.findByTestId('create-new-test-from-spec-header').click()
34+
} else {
35+
cy.get('@runnable-wrapper').realHover().findByTestId('create-new-test-from-suite').click()
36+
}
37+
3338
cy.findByTestId('studio-panel').should('be.visible')
3439
cy.findByTestId('new-test-button').should('be.visible')
3540
} else {

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

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ import { launchStudio, loadProjectAndRunSpec, assertClosingPanelWithoutChanges }
22

33
const urlPrompt = '// Visit a page by entering a url in the address bar or typing a cy.visit command here'
44

5+
const inputNewTestName = (name: string = 'new-test') => {
6+
cy.findByTestId('new-test-button').click()
7+
cy.findByTestId('test-name-input').type(name)
8+
cy.findByTestId('create-test-button').click()
9+
10+
// verify recording is enabled to ensure the panel is fully ready
11+
cy.findByTestId('record-button-recording').should('have.text', 'Recording...')
12+
13+
cy.get('.studio-single-test-container').should('be.visible')
14+
}
15+
516
describe('Cypress Studio', () => {
617
function incrementCounter (initialCount: number) {
718
cy.getAutIframe().within(() => {
@@ -165,7 +176,7 @@ describe('studio functionality', () => {
165176
})
166177

167178
it('does not enter single test mode when creating a new test', () => {
168-
launchStudio({ specName: 'spec-w-multiple-tests.cy.js', createNewTest: true })
179+
launchStudio({ specName: 'spec-w-multiple-tests.cy.js', createNewTestFromSuite: true })
169180

170181
// verify we are not in single test mode
171182
cy.get('.runnable-title').should('have.length', 4)
@@ -175,26 +186,22 @@ describe('studio functionality', () => {
175186
cy.get('.runnable-title').its(3).should('contain.text', 'visits a basic html page 3')
176187
})
177188

178-
it('creates a new test from an empty spec with url already defined', () => {
179-
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTest: true })
189+
it('creates a new test from spec header', () => {
190+
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSpecHeader: true })
180191

181-
cy.findByTestId('new-test-button').click()
182-
cy.findByTestId('test-name-input').type('new-test')
183-
cy.findByTestId('create-test-button').click()
192+
inputNewTestName()
184193

185194
cy.contains('new-test').click()
186195

187-
// verify recording is enabled to ensure the panel is fully ready
188-
cy.findByTestId('record-button-recording').should('have.text', 'Recording...')
189-
190-
cy.get('.studio-single-test-container').should('be.visible')
191-
192196
cy.percySnapshot()
193197

194-
incrementCounter(0)
198+
cy.get('.cm-content').invoke('text', 'cy.visit("cypress/e2e/index.html")')
195199

196200
cy.findByTestId('studio-save-button').click()
197201

202+
// verify recording is enabled to ensure the panel is fully ready
203+
cy.findByTestId('record-button-recording').should('have.text', 'Recording...')
204+
198205
// we should have the commands we executed after we save
199206
cy.withCtx(async (ctx) => {
200207
const spec = await ctx.actions.file.readFileInProject('cypress/e2e/spec-w-visit.cy.js')
@@ -208,19 +215,18 @@ describe('studio functionality', () => {
208215
it('visits a basic html page', () => {
209216
cy.get('h1').should('have.text', 'Hello, Studio!')
210217
})
218+
})
211219
212-
it('new-test', function() {
213-
214-
cy.get('#increment').click();
215-
});
216-
})`.trim())
220+
it('new-test', function() {
221+
cy.visit("cypress/e2e/index.html")
222+
});`.trim())
217223
})
218224
})
219225

220226
// TODO: this test fails in CI but passes locally
221227
// http://github.com/cypress-io/cypress/issues/31248
222228
it.skip('creates a new test with a url that changes top', function () {
223-
launchStudio({ specName: 'spec-w-foobar.cy.js', createNewTest: true })
229+
launchStudio({ specName: 'spec-w-foobar.cy.js', createNewTestFromSuite: true })
224230

225231
cy.origin('http://foobar.com:4455', () => {
226232
Cypress.require('../support/execute-spec')
@@ -294,21 +300,12 @@ describe('studio functionality', () => {
294300
})
295301

296302
it('creates a new test for a specific suite with the url already defined', () => {
297-
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTest: true })
303+
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSuite: true })
298304

299305
// create a new test from a specific suite
300-
cy.findByTestId('create-new-test-button').click()
301-
302-
cy.findByTestId('new-test-button').click()
303-
cy.findByTestId('test-name-input').type('new-test')
304-
cy.findByTestId('create-test-button').click()
306+
cy.findByTestId('create-new-test-from-suite').click()
305307

306-
cy.contains('new-test').click()
307-
308-
// verify recording is enabled to ensure the panel is fully ready
309-
cy.findByTestId('record-button-recording').should('have.text', 'Recording...')
310-
311-
cy.get('.studio-single-test-container').should('be.visible')
308+
inputNewTestName()
312309

313310
cy.percySnapshot()
314311

@@ -618,11 +615,9 @@ describe('studio functionality', () => {
618615
})
619616

620617
it('updates the AUT url when creating a new test', () => {
621-
launchStudio({ specName: 'navigation.cy.js', createNewTest: true })
618+
launchStudio({ specName: 'navigation.cy.js', createNewTestFromSuite: true })
622619

623-
cy.findByTestId('new-test-button').click()
624-
cy.findByTestId('test-name-input').type('new-test')
625-
cy.findByTestId('create-test-button').click()
620+
inputNewTestName()
626621

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

@@ -650,7 +645,7 @@ describe('studio functionality', () => {
650645
})
651646

652647
it('update the url with the suiteId and studio parameters when entering studio with a suite', () => {
653-
launchStudio({ createNewTest: true })
648+
launchStudio({ createNewTestFromSuite: true })
654649

655650
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=')
656651
})
@@ -663,9 +658,7 @@ describe('studio functionality', () => {
663658
cy.location().its('hash').should('contain', 'suiteId=r1').and('contain', 'studio=')
664659

665660
// create a new test in the root suite
666-
cy.findByTestId('new-test-button').click()
667-
cy.findByTestId('test-name-input').type('new-test')
668-
cy.findByTestId('create-test-button').click()
661+
inputNewTestName()
669662

670663
// the studio url parameters should be removed
671664
cy.location().its('hash').and('not.contain', 'suiteId=').and('contain', 'studio=').and('contain', 'testId=r2')
@@ -745,7 +738,7 @@ describe('studio functionality', () => {
745738
})
746739

747740
it('removes the studio url parameters when closing studio new test', () => {
748-
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTest: true })
741+
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSuite: true })
749742

750743
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=')
751744

packages/reporter/cypress/e2e/suites.cy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ describe('suites', () => {
152152
cy.contains('nested suite 1')
153153
.closest('.runnable-wrapper')
154154
.realHover()
155-
.get('[data-cy="create-new-test-button"]')
155+
.get('[data-cy="create-new-test-from-suite"]')
156156
.should('be.visible')
157157
})
158158

@@ -161,7 +161,7 @@ describe('suites', () => {
161161

162162
cy.contains('suite 1').parents('.collapsible-header')
163163
.realHover().within(() => {
164-
cy.get('[data-cy="create-new-test-button"]').click()
164+
cy.get('[data-cy="create-new-test-from-suite"]').click()
165165
})
166166

167167
cy.wrap(runner.emit).should('be.calledWith', 'studio:init:suite', 'r2')

packages/reporter/cypress/e2e/tests.cy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,14 @@ describe('tests', () => {
216216
visitAndRenderReporter(true, false, '__all')
217217

218218
cy.contains('suite 1').realHover()
219-
cy.get('[data-cy="create-new-test-button"]').should('not.exist')
219+
cy.get('[data-cy="create-new-test-from-suite"]').should('not.exist')
220220
})
221221

222222
it('shows new test button in suites when running a single spec', () => {
223223
visitAndRenderReporter(true, false, 'relative/path/to/foo.js')
224224

225225
cy.contains('suite 1').realHover()
226-
cy.get('[data-cy="create-new-test-button"]').should('exist')
226+
cy.get('[data-cy="create-new-test-from-suite"]').should('exist')
227227
})
228228
})
229229
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Button from '@cypress-design/react-button'
2+
import { IconActionAddMedium } from '@cypress-design/react-icon'
3+
import React, { MouseEvent, useCallback } from 'react'
4+
import events from '../lib/events'
5+
import cs from 'classnames'
6+
7+
export const CreateNewTestButton = ({ suiteId, dataCy }: { suiteId: string, dataCy?: string }) => {
8+
const _launchStudio = useCallback((e: MouseEvent) => {
9+
e.preventDefault()
10+
e.stopPropagation()
11+
12+
events.emit('studio:init:suite', suiteId)
13+
}, [events, suiteId])
14+
15+
return (
16+
<Button data-cy={dataCy} size='20' onClick={_launchStudio} variant='outline-dark' className={cs('launch-studio-button')} >
17+
<IconActionAddMedium strokeColor='gray-500' />
18+
New Test
19+
</Button>
20+
)
21+
}

packages/reporter/src/runnables/runnable-and-suite.tsx

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import cs from 'classnames'
22
import _ from 'lodash'
33
import { observer } from 'mobx-react'
4-
import React, { MouseEvent, useCallback, useMemo } from 'react'
4+
import React, { useCallback, useMemo } from 'react'
55

66
import appState from '../lib/app-state'
77
import events, { Events } from '../lib/events'
@@ -11,9 +11,9 @@ import Collapsible, { CollapsibleHeaderComponentProps } from '../collapsible/col
1111
import type SuiteModel from './suite-model'
1212
import type TestModel from '../test/test-model'
1313

14-
import { IconActionAddMedium, IconChevronDownMedium, IconChevronRightMedium, IconObjectStackFailed, IconObjectStackPassed, IconObjectStackQueued, IconObjectStackRunning, IconObjectStackSkipped, WindiColor } from '@cypress-design/react-icon'
15-
import Button from '@cypress-design/react-button'
14+
import { IconChevronDownMedium, IconChevronRightMedium, IconObjectStackFailed, IconObjectStackPassed, IconObjectStackQueued, IconObjectStackRunning, IconObjectStackSkipped, WindiColor } from '@cypress-design/react-icon'
1615
import { RunnableArray } from './runnables-store'
16+
import { CreateNewTestButton } from '../header/CreateNewTestButton'
1717

1818
// should only show connection dots if the current runnable is a test and the next runnable is a test and is not the last runnable
1919
export const shouldShowConnectionDots = (runnables: RunnableArray, runnable: SuiteModel | TestModel, runnableIndex: number) => {
@@ -35,13 +35,6 @@ const headerIconDefaultProps = {
3535
}
3636

3737
const Suite: React.FC<SuiteProps> = observer(({ eventManager = events, model, studioEnabled, canSaveStudioLogs, spec }: SuiteProps) => {
38-
const _launchStudio = useCallback((e: MouseEvent) => {
39-
e.preventDefault()
40-
e.stopPropagation()
41-
42-
eventManager.emit('studio:init:suite', model.id)
43-
}, [eventManager, model.id])
44-
4538
const headerIconStyle = {
4639
marginTop: '1px',
4740
}
@@ -85,16 +78,13 @@ const Suite: React.FC<SuiteProps> = observer(({ eventManager = events, model, st
8578
<span className='runnable-title'>{model.title}</span>
8679
{(studioEnabled && !appState.studioActive && spec?.relative !== '__all') && (
8780
<>
88-
<Button data-cy='create-new-test-button' size='20' onClick={_launchStudio} variant='outline-dark' className={cs('launch-studio-button')} >
89-
<IconActionAddMedium strokeColor='gray-500' />
90-
New Test
91-
</Button>
81+
<CreateNewTestButton suiteId={model.id} dataCy='create-new-test-from-suite' />
9282
<span className='button-hover-shadow' />
9383
</>
9484
)}
9585
</>
9686
)
97-
}, [getHeaderIcon, model.title, studioEnabled, appState.studioActive, _launchStudio])
87+
}, [getHeaderIcon, model.title, studioEnabled, appState.studioActive])
9888

9989
const runnablesList = useMemo(() => (
10090
<ul className='runnables'>

packages/reporter/src/shared/SpecFileName.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@
99
position: relative;
1010
background: $gray-1100;
1111

12+
.open-in-ide-button {
13+
right: 100px;
14+
}
15+
16+
.button-hover-shadow {
17+
right: 100px;
18+
}
19+
20+
.launch-studio-button {
21+
@include new-test-button;
22+
}
23+
1224
&:after {
1325
content: none;
1426
}
@@ -29,6 +41,10 @@
2941
opacity: 1;
3042
}
3143

44+
.launch-studio-button {
45+
opacity: 1;
46+
}
47+
3248
.button-hover-shadow {
3349
opacity: 1;
3450
}

packages/reporter/src/shared/SpecFileName.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react'
22
import { getFilenameParts } from '../lib/util'
33
import { OpenFileInIDEButton } from '../header/OpenFileInIDEButton'
4+
import { CreateNewTestButton } from '../header/CreateNewTestButton'
45

56
const displayFileName = (spec: Cypress.Cypress['spec']) => {
67
const specParts = getFilenameParts(spec.name)
@@ -27,5 +28,6 @@ export const SpecFileName = ({ spec }: { spec: Cypress.Cypress['spec'] }) => {
2728
return <div className='spec-file-name'>
2829
{fileDetails.displayFile || fileDetails.originalFile}{!!fileDetails.line && `:${fileDetails.line}`}{!!fileDetails.column && `:${fileDetails.column}`}
2930
<OpenFileInIDEButton fileDetails={fileDetails} />
31+
<CreateNewTestButton suiteId='r1' dataCy='create-new-test-from-spec-header' />
3032
</div>
3133
}

0 commit comments

Comments
 (0)