Skip to content

Commit e1ca574

Browse files
internal: (studio) allow tests to be created in specs that have .only tests (#32118)
* internal: (studio) allow tests to be created in specs that have .only tests * fix stop-only * add system test spec, add test, fix bugs * clean up unused logic * fix issue * another fix * refactors * update variable name, consolidate logic --------- Co-authored-by: Jennifer Shehane <[email protected]>
1 parent f35e029 commit e1ca574

File tree

4 files changed

+97
-7
lines changed

4 files changed

+97
-7
lines changed

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,42 @@ describe('Studio Cloud', () => {
1111
})
1212
})
1313

14+
it('allows .only tests to be edited in studio', () => {
15+
loadProjectAndRunSpec({ specName: 'spec-with-only.cy.js' })
16+
17+
// verify the test is the only one that runs
18+
cy.get('.test').should('have.length', 1)
19+
cy.get('.test').contains('should be the only test to run normally').should('be.visible')
20+
21+
// open edit in studio
22+
cy.contains('should be the only test to run normally')
23+
.closest('.runnable-wrapper')
24+
.findByTestId('launch-studio')
25+
.click()
26+
27+
cy.findByTestId('studio-panel').should('be.visible')
28+
29+
cy.findByTestId('studio-single-test-title').should('have.text', 'should be the only test to run normally')
30+
})
31+
32+
it('creates and runs new tests in studio mode when there is a .only test in the spec file', () => {
33+
loadProjectAndRunSpec({ specName: 'spec-with-only.cy.js' })
34+
35+
cy.get('.test').should('have.length', 1)
36+
cy.get('.test').contains('should be the only test to run normally').should('be.visible')
37+
38+
// launch studio and create a new test
39+
cy.findByTestId('studio-button').click()
40+
cy.findByTestId('studio-panel').should('be.visible').within(() => {
41+
cy.contains('button', 'New test').click()
42+
cy.get('[data-cy="test-name-input"]').type('new test{enter}')
43+
})
44+
45+
cy.get('.spec-name').should('have.text', 'spec-with-only')
46+
// our new test runs in studio mode even though it doesn't have a .only
47+
cy.get('[data-cy="studio-single-test-title"]').should('have.text', 'new test')
48+
})
49+
1450
it('immediately loads the studio panel from existing test', () => {
1551
const deferred = pDefer()
1652

packages/app/src/store/studio-store.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ interface StudioRecorderState {
109109
showUrlPrompt: boolean
110110
cloudStudioRequested: boolean
111111
cloudStudioSessionId?: string
112+
_isStudioCreatedTest: boolean
112113
newTestLineNumber?: number
113114
}
114115

@@ -128,6 +129,8 @@ export const useStudioStore = defineStore('studioRecorder', {
128129
showUrlPrompt: true,
129130
cloudStudioRequested: false,
130131
cloudStudioSessionId: undefined,
132+
newTestLineNumber: undefined,
133+
_isStudioCreatedTest: false,
131134
}
132135
},
133136

@@ -232,15 +235,21 @@ export const useStudioStore = defineStore('studioRecorder', {
232235
initialize () {
233236
if (this.newTestLineNumber) {
234237
getCypress().runner.setNewTestLineNumber(this.newTestLineNumber)
238+
// Creating a new test - need to bypass .only filtering
239+
getCypress().runner.setIsStudioCreatedTest(true)
240+
this._isStudioCreatedTest = true
235241
} else if (this.testId) {
236242
getCypress().runner.setOnlyTestId(this.testId)
243+
getCypress().runner.setIsStudioCreatedTest(this._isStudioCreatedTest)
237244
}
238245
},
239246

240247
interceptTest (test) {
241248
// if this test is the one we created, we can just set the test id
242-
if (this.newTestLineNumber && test.invocationDetails?.line === this.newTestLineNumber) {
249+
if ((this.newTestLineNumber && test.invocationDetails?.line === this.newTestLineNumber) || this.suiteId) {
250+
this._isStudioCreatedTest = true
243251
this.setTestId(test.id)
252+
getCypress().runner.setIsStudioCreatedTest(true)
244253
}
245254

246255
if (this.testId) {
@@ -284,6 +293,7 @@ export const useStudioStore = defineStore('studioRecorder', {
284293
this._currentId = 1
285294
this.isFailed = false
286295
this.showUrlPrompt = true
296+
this._isStudioCreatedTest = false
287297

288298
this._maybeResetRunnables()
289299
},

packages/driver/src/cypress/runner.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ const RUNNABLE_PROPS = [
3131
const debug = debugFn('cypress:driver:runner')
3232
const debugErrors = debugFn('cypress:driver:errors')
3333

34+
// detect studio mode from URL parameters
35+
const isStudioMode = () => {
36+
try {
37+
if (typeof window === 'undefined') return false
38+
39+
const url = new URL(window.location.href)
40+
const hashParams = new URLSearchParams(url.hash.slice(1))
41+
42+
// studio mode is active if there's a 'studio' parameter
43+
return hashParams.has('studio')
44+
} catch (err) {
45+
return false
46+
}
47+
}
48+
3449
const RUNNER_EVENTS = [
3550
TEST_BEFORE_RUN_ASYNC_EVENT,
3651
TEST_BEFORE_RUN_EVENT,
@@ -636,7 +651,7 @@ const pruneEmptySuites = (rootSuite, testFilter: NonNullable<TestFilter>) => {
636651
return totalUnfilteredTests
637652
}
638653

639-
const normalizeAll = (suite, initialTests = {}, testFilter, setTestsById, setTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber) => {
654+
const normalizeAll = (suite, initialTests = {}, testFilter, setTestsById, setTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber, getIsStudioCreatedTest) => {
640655
let totalUnfilteredTests = 0
641656

642657
// Empty suites don't have any impact in run mode so let's avoid this extra work.
@@ -662,7 +677,7 @@ const normalizeAll = (suite, initialTests = {}, testFilter, setTestsById, setTes
662677
// traversing through it multiple times
663678
const tests: Record<string, any> = {}
664679

665-
const normalizedSuite = normalize(suite, tests, initialTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber)
680+
const normalizedSuite = normalize(suite, tests, initialTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber, getIsStudioCreatedTest)
666681

667682
if (setTestsById) {
668683
// use callback here to hand back
@@ -702,7 +717,7 @@ const normalizeAll = (suite, initialTests = {}, testFilter, setTestsById, setTes
702717
return normalizedSuite
703718
}
704719

705-
const normalize = (runnable, tests, initialTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber) => {
720+
const normalize = (runnable, tests, initialTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber, getIsStudioCreatedTest) => {
706721
const normalizeRunnable = (runnable) => {
707722
if (!runnable.id) {
708723
runnable.id = getRunnableId()
@@ -771,7 +786,9 @@ const normalize = (runnable, tests, initialTests, getRunnableId, getHookId, setO
771786
setOnlyTestId(runnable.id)
772787
}
773788

774-
if ((runnable.type !== 'suite') || !hasOnly(runnable)) {
789+
const shouldBypassOnly = (isStudioMode() && getIsStudioCreatedTest())
790+
791+
if ((runnable.type !== 'suite') || !hasOnly(runnable) || shouldBypassOnly) {
775792
if (runnable.type === 'test' && (!getOnlyTestId() || runnable.id === getOnlyTestId())) {
776793
push(runnable)
777794
}
@@ -792,7 +809,7 @@ const normalize = (runnable, tests, initialTests, getRunnableId, getHookId, setO
792809
_.each({ tests: runnableTests, suites: runnableSuites }, (_runnables, type) => {
793810
if (runnable[type]) {
794811
return normalizedRunnable[type] = _.compact(_.map(_runnables, (childRunnable) => {
795-
const normalizedChild = normalize(childRunnable, tests, initialTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber)
812+
const normalizedChild = normalize(childRunnable, tests, initialTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber, getIsStudioCreatedTest)
796813

797814
if (type === 'tests' && onlyIdMode()) {
798815
if (normalizedChild.id === getOnlyTestId()) {
@@ -881,7 +898,7 @@ const normalize = (runnable, tests, initialTests, getRunnableId, getHookId, setO
881898
suite.suites = []
882899

883900
normalizedSuite.suites = _.compact(_.map(suiteSuites, (childSuite) => {
884-
const normalizedChildSuite = normalize(childSuite, tests, initialTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber)
901+
const normalizedChildSuite = normalize(childSuite, tests, initialTests, getRunnableId, getHookId, setOnlyTestId, getOnlyTestId, getNewTestLineNumber, getIsStudioCreatedTest)
885902

886903
if ((suite._onlySuites.indexOf(childSuite) !== -1) || filterOnly(normalizedChildSuite, childSuite)) {
887904
if (onlyIdMode()) {
@@ -1314,6 +1331,7 @@ export default {
13141331
let _startTime: string | null = null
13151332
let _onlyTestId = null
13161333
let _newTestLineNumber = null
1334+
let _isStudioCreatedTest = false
13171335

13181336
const getRunnableId = () => {
13191337
return `r${++_runnableId}`
@@ -1378,6 +1396,12 @@ export default {
13781396

13791397
const getNewTestLineNumber = () => _newTestLineNumber
13801398

1399+
const setIsStudioCreatedTest = (isCreated) => {
1400+
_isStudioCreatedTest = isCreated
1401+
}
1402+
1403+
const getIsStudioCreatedTest = () => _isStudioCreatedTest
1404+
13811405
const abort = () => {
13821406
// abort the run
13831407
_runner.abort()
@@ -1562,6 +1586,8 @@ export default {
15621586
setOnlyTestId,
15631587
setNewTestLineNumber,
15641588
getNewTestLineNumber,
1589+
setIsStudioCreatedTest,
1590+
getIsStudioCreatedTest,
15651591
normalizeAll (tests, skipCollectingLogs, testFilter) {
15661592
_skipCollectingLogs = skipCollectingLogs
15671593
// if we have an uncaught error then slice out
@@ -1590,6 +1616,7 @@ export default {
15901616
setOnlyTestId,
15911617
getOnlyTestId,
15921618
getNewTestLineNumber,
1619+
getIsStudioCreatedTest,
15931620
)
15941621
},
15951622

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
describe('spec with .only tests', () => {
2+
// eslint-disable-next-line mocha/no-exclusive-tests
3+
it.only('should be the only test to run normally', () => {
4+
cy.visit('cypress/e2e/index.html')
5+
cy.get('h1').should('contain', 'Hello World')
6+
})
7+
8+
it('should be skipped in normal mode', () => {
9+
cy.visit('cypress/e2e/index.html')
10+
cy.get('p').should('contain', 'Count is 0')
11+
})
12+
13+
it('another test that should be skipped', () => {
14+
cy.visit('cypress/e2e/index.html')
15+
cy.get('#increment').click()
16+
})
17+
})

0 commit comments

Comments
 (0)