diff --git a/packages/app/cypress/e2e/studio/studio-cloud.cy.ts b/packages/app/cypress/e2e/studio/studio-cloud.cy.ts index ca8996ccef45..af22bd7993d0 100644 --- a/packages/app/cypress/e2e/studio/studio-cloud.cy.ts +++ b/packages/app/cypress/e2e/studio/studio-cloud.cy.ts @@ -505,4 +505,44 @@ describe('studio functionality', () => { }) }) }) + + it('persists sessionId across page refresh', () => { + launchStudio() + + cy.findByTestId('studio-panel').should('be.visible') + + cy.location().its('hash').should('contain', 'sessionId=') + + let originalSessionId: string + + cy.location('hash').then((hash) => { + const urlParams = new URLSearchParams(hash) + + originalSessionId = urlParams.get('sessionId')! + + expect(originalSessionId).to.be.a('string') + expect(originalSessionId).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i) + }) + + cy.reload() + + cy.waitForSpecToFinish() + + cy.findByTestId('studio-panel').should('be.visible') + + cy.location().its('hash').should('contain', 'sessionId=') + + cy.location('hash').then((hash) => { + const urlParams = new URLSearchParams(hash) + const persistedSessionId = urlParams.get('sessionId') + + expect(persistedSessionId).to.equal(originalSessionId) + }) + + cy.findByTestId('studio-header-studio-button').click() + + cy.location().its('hash').should('not.contain', 'sessionId=') + + cy.findByTestId('studio-panel').should('not.exist') + }) }) diff --git a/packages/app/cypress/e2e/studio/studio.cy.ts b/packages/app/cypress/e2e/studio/studio.cy.ts index aea6da8ce70e..5d0e9c8fb498 100644 --- a/packages/app/cypress/e2e/studio/studio.cy.ts +++ b/packages/app/cypress/e2e/studio/studio.cy.ts @@ -664,13 +664,13 @@ describe('studio functionality', () => { it('updates the url with the testId and studio parameters when entering studio with a test', () => { launchStudio() - cy.location().its('hash').should('contain', 'testId=r3').and('contain', 'studio=') + cy.location().its('hash').should('contain', 'testId=r3').and('contain', 'studio=').and('contain', 'sessionId=') }) it('update the url with the suiteId and studio parameters when entering studio with a suite', () => { launchStudio({ createNewTestFromSuite: true }) - cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=') + cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=').and('contain', 'sessionId=') }) it('updates the studio url parameters and displays the single test view after creating a new test', () => { @@ -678,7 +678,7 @@ describe('studio functionality', () => { // open the studio panel to create a new test in the root suite cy.findByTestId('studio-button').click() - cy.location().its('hash').should('contain', 'suiteId=r1').and('contain', 'studio=') + cy.location().its('hash').should('contain', 'suiteId=r1').and('contain', 'studio=').and('contain', 'sessionId=') // create a new test in the root suite inputNewTestName() @@ -694,7 +694,7 @@ describe('studio functionality', () => { it('does not remove the studio url parameters when saving test changes', () => { launchStudio() - cy.location().its('hash').should('contain', 'testId=r3').and('contain', 'studio=') + cy.location().its('hash').should('contain', 'testId=r3').and('contain', 'studio=').and('contain', 'sessionId=') cy.findByTestId('record-button-recording').should('be.visible') @@ -706,7 +706,7 @@ describe('studio functionality', () => { cy.findByTestId('studio-save-button').click() - cy.location().its('hash').should('contain', 'testId=r3').and('contain', 'studio=') + cy.location().its('hash').should('contain', 'testId=r3').and('contain', 'studio=').and('contain', 'sessionId=') }) it('does not remove the studio url parameters if saving fails', () => { @@ -716,7 +716,7 @@ describe('studio functionality', () => { incrementCounter(0) - cy.location().its('hash').should('contain', 'testId=r3').and('contain', 'studio=') + cy.location().its('hash').should('contain', 'testId=r3').and('contain', 'studio=').and('contain', 'sessionId=') // update the spec on the file system by changing the // test name which will cause the save to fail since @@ -757,7 +757,7 @@ describe('studio functionality', () => { cy.findByTestId('studio-header-studio-button').click() - cy.location().its('hash').and('not.contain', 'testId=').and('not.contain', 'studio=') + cy.location().its('hash').and('not.contain', 'testId=').and('not.contain', 'studio=').and('not.contain', 'sessionId=') }) it('does not prompt for a URL until studio is active', () => { diff --git a/packages/app/src/runner/SpecRunnerOpenMode.vue b/packages/app/src/runner/SpecRunnerOpenMode.vue index 8ff277f31d35..19895a993f69 100644 --- a/packages/app/src/runner/SpecRunnerOpenMode.vue +++ b/packages/app/src/runner/SpecRunnerOpenMode.vue @@ -92,7 +92,7 @@ { + // try to restore sessionId from URL parameters + const urlParams = getUrlParams() + const persistedSessionId = urlParams.sessionId || undefined + return { saveModalIsOpen: false, instructionModalIsOpen: false, @@ -128,7 +145,7 @@ export const useStudioStore = defineStore('studioRecorder', { canAccessStudioAI: false, showUrlPrompt: true, cloudStudioRequested: false, - cloudStudioSessionId: undefined, + sessionId: persistedSessionId, newTestLineNumber: undefined, _isStudioCreatedTest: false, } @@ -160,8 +177,14 @@ export const useStudioStore = defineStore('studioRecorder', { this.canAccessStudioAI = canAccessStudioAI }, - setCloudStudioSessionId (cloudStudioSessionId: string) { - this.cloudStudioSessionId = cloudStudioSessionId + setSessionId (sessionId: string) { + this.sessionId = sessionId + this._updateUrlParams(['sessionId']) + }, + + clearSessionId () { + this.sessionId = undefined + this._removeUrlParams(['sessionId']) }, setNewTestLineNumber (newTestLineNumber: number) { @@ -213,7 +236,7 @@ export const useStudioStore = defineStore('studioRecorder', { }, setup (config) { - const studio = this._getUrlParams() + const studio = this.getUrlParams() if (studio.newTestLineNumber) { this.setNewTestLineNumber(studio.newTestLineNumber) @@ -227,6 +250,10 @@ export const useStudioStore = defineStore('studioRecorder', { this._initialUrl = studio.url } + if (studio.sessionId) { + this.sessionId = studio.sessionId + } + // if we have an existing test or are creating a new test, we need to start loading // otherwise if we have a suite, we can just set the studio active if (this.testId || studio.newTestLineNumber) { @@ -308,6 +335,7 @@ export const useStudioStore = defineStore('studioRecorder', { this.clearRunnableIds() this._removeUrlParams() this._initialUrl = undefined + this.clearSessionId() }, startSave () { @@ -436,21 +464,11 @@ export const useStudioStore = defineStore('studioRecorder', { } }, - _getUrlParams () { - const url = new URL(window.location.href) - const hashParams = new URLSearchParams(url.hash) - - const testId = hashParams.get('testId') - const suiteId = hashParams.get('suiteId') - const visitUrl = hashParams.get('url') - const newTestLineNumber = hashParams.get('newTestLineNumber') ? Number(hashParams.get('newTestLineNumber')) : undefined - - return { testId, suiteId, url: visitUrl, newTestLineNumber } - }, + getUrlParams, - _updateUrlParams (filter: string[] = ['testId', 'suiteId', 'url', 'newTestLineNumber']) { + _updateUrlParams (filter: string[] = ['testId', 'suiteId', 'url', 'newTestLineNumber', 'sessionId']) { // if we don't have studio params, we don't need to update them - if (!this.testId && !this.suiteId && !this.url && !this.newTestLineNumber) return + if (!this.testId && !this.suiteId && !this.url && !this.newTestLineNumber && !this.sessionId) return // if we have studio params, we need to remove them before adding them back this._removeUrlParams(filter) @@ -469,7 +487,7 @@ export const useStudioStore = defineStore('studioRecorder', { window.history.replaceState({}, '', url.toString()) }, - _removeUrlParams (filter: string[] = ['testId', 'suiteId', 'url', 'newTestLineNumber']) { + _removeUrlParams (filter: string[] = ['testId', 'suiteId', 'url', 'newTestLineNumber', 'sessionId']) { const url = new URL(window.location.href) const hashParams = new URLSearchParams(url.hash) @@ -482,7 +500,7 @@ export const useStudioStore = defineStore('studioRecorder', { }) // if there are no studio specific params left, we can also remove the studio param - if (!hashParams.has('testId') && !hashParams.has('suiteId') && !hashParams.has('url') && !hashParams.has('newTestLineNumber')) { + if (!hashParams.has('testId') && !hashParams.has('suiteId') && !hashParams.has('url') && !hashParams.has('newTestLineNumber') && !hashParams.has('sessionId')) { hashParams.delete('studio') }