Skip to content

Commit c9c0575

Browse files
committed
internal: (studio) improvements to how studio can access the protocol database
1 parent a1fa6b2 commit c9c0575

File tree

10 files changed

+107
-39
lines changed

10 files changed

+107
-39
lines changed

.circleci/workflows.yml

+5-6
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ mainBuildFilters: &mainBuildFilters
3838
- /^release\/\d+\.\d+\.\d+$/
3939
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
4040
- 'update-v8-snapshot-cache-on-develop'
41-
- 'chore/fix_react_18_deprecation_warnings'
41+
- 'ryanm/chore/full-snapshot-threaded'
4242

4343
# usually we don't build Mac app - it takes a long time
4444
# but sometimes we want to really confirm we are doing the right thing
@@ -49,7 +49,7 @@ macWorkflowFilters: &darwin-workflow-filters
4949
- equal: [ develop, << pipeline.git.branch >> ]
5050
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
5151
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
52-
- equal: [ 'chore/fix_react_18_deprecation_warnings', << pipeline.git.branch >> ]
52+
- equal: [ 'ryanm/chore/full-snapshot-threaded', << pipeline.git.branch >> ]
5353
- matches:
5454
pattern: /^release\/\d+\.\d+\.\d+$/
5555
value: << pipeline.git.branch >>
@@ -60,7 +60,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
6060
- equal: [ develop, << pipeline.git.branch >> ]
6161
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
6262
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
63-
- equal: [ 'chore/fix_react_18_deprecation_warnings', << pipeline.git.branch >> ]
63+
- equal: [ 'ryanm/chore/full-snapshot-threaded', << pipeline.git.branch >> ]
6464
- matches:
6565
pattern: /^release\/\d+\.\d+\.\d+$/
6666
value: << pipeline.git.branch >>
@@ -83,8 +83,7 @@ windowsWorkflowFilters: &windows-workflow-filters
8383
- equal: [ develop, << pipeline.git.branch >> ]
8484
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
8585
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
86-
- equal: [ 'chore/fix_react_18_deprecation_warnings', << pipeline.git.branch >> ]
87-
- equal: [ 'cacie/fix-du', << pipeline.git.branch >> ]
86+
- equal: [ 'ryanm/chore/full-snapshot-threaded', << pipeline.git.branch >> ]
8887
- matches:
8988
pattern: /^release\/\d+\.\d+\.\d+$/
9089
value: << pipeline.git.branch >>
@@ -158,7 +157,7 @@ commands:
158157
name: Set environment variable to determine whether or not to persist artifacts
159158
command: |
160159
echo "Setting SHOULD_PERSIST_ARTIFACTS variable"
161-
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "chore/fix_react_18_deprecation_warnings" ]]; then
160+
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "ryanm/chore/full-snapshot-threaded" ]]; then
162161
export SHOULD_PERSIST_ARTIFACTS=true
163162
fi' >> "$BASH_ENV"
164163
# You must run `setup_should_persist_artifacts` command and be using bash before running this command

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

+20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export interface StudioPanelProps {
22
canAccessStudioAI: boolean
3+
useStudioEventManager?: StudioEventManagerShape
4+
useStudioAIStream?: StudioAIStreamShape
35
}
46

57
export type StudioPanelShape = (props: StudioPanelProps) => JSX.Element
@@ -9,3 +11,21 @@ export interface StudioAppDefaultShape {
911
// transferred to the Cypress app
1012
StudioPanel: StudioPanelShape
1113
}
14+
15+
export interface StudioEventManagerProps {
16+
Cypress: Cypress.Cypress & CyEventEmitter
17+
}
18+
19+
export type RunnerStatus = 'running' | 'finished'
20+
21+
export type StudioEventManagerShape = (
22+
props: StudioEventManagerProps
23+
) => RunnerStatus
24+
25+
export interface StudioAIStreamProps {
26+
canAccessStudioAI: boolean
27+
AIOutputRef: React.RefObject<HTMLTextAreaElement>
28+
runnerStatus: RunnerStatus
29+
}
30+
31+
export type StudioAIStreamShape = (props: StudioAIStreamProps) => void

packages/server/lib/cloud/studio.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import os from 'os'
66
import { agent } from '@packages/network'
77
import Debug from 'debug'
88
import { requireScript } from './require_script'
9-
import type Database from 'better-sqlite3'
9+
import path from 'path'
1010

1111
interface StudioServer { default: StudioServerDefaultShape }
1212

@@ -38,8 +38,8 @@ export class StudioManager implements StudioManagerShape {
3838
return manager
3939
}
4040

41-
setProtocolDb (db: Database.Database): void {
42-
this.invokeSync('setProtocolDb', { isEssential: true }, db)
41+
setProtocolDbPath (protocolDbPath: string): void {
42+
this.invokeSync('setProtocolDbPath', { isEssential: true }, protocolDbPath)
4343
}
4444

4545
async setup ({ script, studioPath, studioHash, projectSlug, cloudApi }: SetupOptions): Promise<void> {
@@ -49,6 +49,7 @@ export class StudioManager implements StudioManagerShape {
4949
studioPath,
5050
projectSlug,
5151
cloudApi,
52+
betterSqlite3Path: path.dirname(require.resolve('better-sqlite3/package.json')),
5253
})
5354

5455
this._studioHash = studioHash
@@ -65,6 +66,14 @@ export class StudioManager implements StudioManagerShape {
6566
return (await this.invokeAsync('canAccessStudioAI', { isEssential: true }, browser)) ?? false
6667
}
6768

69+
async initializeStudioAI (): Promise<void> {
70+
await this.invokeAsync('initializeStudioAI', { isEssential: true })
71+
}
72+
73+
async destroy (): Promise<void> {
74+
await this.invokeAsync('destroy', { isEssential: true })
75+
}
76+
6877
private async reportError (error: Error): Promise<void> {
6978
try {
7079
const payload: StudioErrorReport = {

packages/server/lib/project-base.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -432,12 +432,14 @@ export class ProjectBase extends EE {
432432

433433
onStudioInit: async () => {
434434
if (this.spec && this.ctx.coreData.studio?.protocolManager) {
435-
const canAccessStudioAI = await this.ctx.coreData.studio?.canAccessStudioAI(this.browser) ?? false
435+
const canAccessStudioAI = await this.ctx.coreData.studio.canAccessStudioAI(this.browser) ?? false
436436

437437
if (!canAccessStudioAI) {
438438
return { canAccessStudioAI }
439439
}
440440

441+
await this.ctx.coreData.studio.initializeStudioAI()
442+
441443
this.protocolManager = this.ctx.coreData.studio?.protocolManager
442444
this.protocolManager?.setupProtocol()
443445
this.protocolManager?.beforeSpec({
@@ -447,8 +449,8 @@ export class ProjectBase extends EE {
447449

448450
await browsers.connectProtocolToBrowser({ browser: this.browser, foundBrowsers: this.options.browsers, protocolManager: this.protocolManager })
449451

450-
if (this.protocolManager.db) {
451-
this.ctx.coreData.studio?.setProtocolDb(this.protocolManager.db)
452+
if (this.protocolManager.dbPath) {
453+
this.ctx.coreData.studio?.setProtocolDbPath(this.protocolManager.dbPath)
452454
}
453455

454456
return { canAccessStudioAI: true }
@@ -464,6 +466,7 @@ export class ProjectBase extends EE {
464466
await browsers.closeProtocolConnection({ browser: this.browser, foundBrowsers: this.options.browsers })
465467
this.protocolManager?.close()
466468
this.protocolManager = undefined
469+
await this.ctx.coreData.studio.destroy()
467470
}
468471
},
469472

packages/server/test/support/fixtures/cloud/studio/test-studio.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1-
import type { StudioServerShape, StudioServerDefaultShape, StudioBrowser } from '@packages/types'
2-
import type Database from 'better-sqlite3'
1+
import type { StudioServerShape, StudioServerDefaultShape } from '@packages/types'
32
import type { Router } from 'express'
43

54
class StudioServer implements StudioServerShape {
65
initializeRoutes (router: Router): void {
76

87
}
98

10-
canAccessStudioAI (browser: StudioBrowser): Promise<boolean> {
9+
canAccessStudioAI (browser: Cypress.Browser): Promise<boolean> {
1110
return Promise.resolve(true)
1211
}
1312

14-
setProtocolDb (db: Database.Database): void {
13+
setProtocolDbPath (dbPath: string): void {
14+
}
15+
16+
initializeStudioAI (): Promise<void> {
17+
return Promise.resolve()
18+
}
19+
20+
destroy (): Promise<void> {
21+
return Promise.resolve()
1522
}
1623
}
1724

packages/server/test/unit/cloud/studio_spec.ts

+26-6
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,35 @@ describe('lib/cloud/studio', () => {
172172
})
173173
})
174174

175-
describe('setProtocolDb', () => {
176-
it('sets the protocol database on the studio server', () => {
177-
const mockDb = { test: 'db' }
175+
describe('setProtocolDbPath', () => {
176+
it('sets the protocol database path on the studio server', () => {
177+
const mockPath = '/path/to/db'
178178

179-
sinon.stub(studio, 'setProtocolDb')
179+
sinon.stub(studio, 'setProtocolDbPath')
180180

181-
studioManager.setProtocolDb(mockDb as any)
181+
studioManager.setProtocolDbPath(mockPath)
182182

183-
expect(studio.setProtocolDb).to.be.calledWith(mockDb)
183+
expect(studio.setProtocolDbPath).to.be.calledWith(mockPath)
184+
})
185+
})
186+
187+
describe('initializeStudioAI', () => {
188+
it('initializes Studio AI on the studio server', async () => {
189+
sinon.stub(studio, 'initializeStudioAI').resolves()
190+
191+
await studioManager.initializeStudioAI()
192+
193+
expect(studio.initializeStudioAI).to.be.called
194+
})
195+
})
196+
197+
describe('destroy', () => {
198+
it('destroys the studio server', async () => {
199+
sinon.stub(studio, 'destroy').resolves()
200+
201+
await studioManager.destroy()
202+
203+
expect(studio.destroy).to.be.called
184204
})
185205
})
186206
})

packages/server/test/unit/project_spec.js

+20-14
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('lib/project-base', () => {
4343
this.testStudioManager = {
4444
initializeRoutes: () => {},
4545
status: 'INITIALIZED',
46+
destroy: () => Promise.resolve(),
4647
}
4748

4849
sinon.stub(studio, 'getAndInitializeStudioManager').resolves(this.testStudioManager)
@@ -736,18 +737,20 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
736737
it('passes onStudioInit callback with AI enabled and a protocol manager', async function () {
737738
const mockSetupProtocol = sinon.stub()
738739
const mockBeforeSpec = sinon.stub()
739-
const mockAccessStudioLLM = sinon.stub().resolves(true)
740-
const mockSetProtocolDb = sinon.stub()
740+
const mockAccessStudioAI = sinon.stub().resolves(true)
741+
const mockSetProtocolDbPath = sinon.stub()
742+
const mockInitializeStudioAI = sinon.stub().resolves()
741743

742744
this.project.spec = {}
743745
this.project.ctx.coreData.studio = {
744-
canAccessStudioAI: mockAccessStudioLLM,
746+
canAccessStudioAI: mockAccessStudioAI,
745747
protocolManager: {
746748
setupProtocol: mockSetupProtocol,
747749
beforeSpec: mockBeforeSpec,
748-
db: { test: 'db' },
750+
dbPath: 'test-db-path',
749751
},
750-
setProtocolDb: mockSetProtocolDb,
752+
setProtocolDbPath: mockSetProtocolDbPath,
753+
initializeStudioAI: mockInitializeStudioAI,
751754
}
752755

753756
sinon.stub(browsers, 'connectProtocolToBrowser').resolves()
@@ -783,7 +786,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
783786

784787
expect(mockSetupProtocol).to.be.calledOnce
785788
expect(mockBeforeSpec).to.be.calledOnce
786-
expect(mockAccessStudioLLM).to.be.calledWith({
789+
expect(mockAccessStudioAI).to.be.calledWith({
787790
family: 'chromium',
788791
name: 'chrome',
789792
channel: 'stable',
@@ -796,17 +799,18 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
796799
})
797800

798801
expect(this.project['_protocolManager']).to.eq(this.project.ctx.coreData.studio.protocolManager)
799-
expect(mockSetProtocolDb).to.be.calledWith({ test: 'db' })
802+
expect(mockSetProtocolDbPath).to.be.calledWith('test-db-path')
803+
expect(mockInitializeStudioAI).to.be.called
800804
})
801805

802806
it('passes onStudioInit callback with AI enabled but no protocol manager', async function () {
803807
const mockSetupProtocol = sinon.stub()
804808
const mockBeforeSpec = sinon.stub()
805-
const mockAccessStudioLLM = sinon.stub().resolves(true)
809+
const mockAccessStudioAI = sinon.stub().resolves(true)
806810

807811
this.project.spec = {}
808812
this.project.ctx.coreData.studio = {
809-
canAccessStudioAI: mockAccessStudioLLM,
813+
canAccessStudioAI: mockAccessStudioAI,
810814
}
811815

812816
this.project.browser = {
@@ -836,20 +840,20 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
836840

837841
expect(mockSetupProtocol).not.to.be.called
838842
expect(mockBeforeSpec).not.to.be.called
839-
expect(mockAccessStudioLLM).not.to.be.called
843+
expect(mockAccessStudioAI).not.to.be.called
840844

841845
expect(browsers.connectProtocolToBrowser).not.to.be.called
842846
expect(this.project['_protocolManager']).to.be.undefined
843847
})
844848

845-
it('passes onStudioInit callback with llm disabled', async function () {
849+
it('passes onStudioInit callback with AI disabled', async function () {
846850
const mockSetupProtocol = sinon.stub()
847851
const mockBeforeSpec = sinon.stub()
848-
const mockAccessStudioLLM = sinon.stub().resolves(false)
852+
const mockAccessStudioAI = sinon.stub().resolves(false)
849853

850854
this.project.spec = {}
851855
this.project.ctx.coreData.studio = {
852-
canAccessStudioAI: mockAccessStudioLLM,
856+
canAccessStudioAI: mockAccessStudioAI,
853857
protocolManager: {
854858
setupProtocol: mockSetupProtocol,
855859
beforeSpec: mockBeforeSpec,
@@ -887,9 +891,11 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
887891

888892
it('passes onStudioDestroy callback', async function () {
889893
const mockClose = sinon.stub()
894+
const mockDestroy = sinon.stub().resolves()
890895

891896
this.project.ctx.coreData.studio = {
892897
protocolManager: {},
898+
destroy: mockDestroy,
893899
}
894900

895901
sinon.stub(browsers, 'closeProtocolConnection').resolves()
@@ -928,7 +934,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
928934
})
929935

930936
expect(mockClose).to.be.calledOnce
931-
937+
expect(mockDestroy).to.be.calledOnce
932938
expect(this.project['_protocolManager']).to.be.undefined
933939
})
934940
})

packages/types/src/protocol.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export interface ProtocolManagerShape extends AppCaptureProtocolCommon {
138138
uploadCaptureArtifact(artifact: CaptureArtifact): Promise<UploadCaptureArtifactResult | undefined>
139139
connectToBrowser (cdpClient: CDPClient): Promise<void>
140140
close (): void
141-
db?: Database.Database
141+
dbPath?: string
142142
}
143143

144144
type Response = {

packages/types/src/studio/studio-server-types.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import type { Router } from 'express'
44
import type { AxiosInstance } from 'axios'
5-
import type Database from 'better-sqlite3'
65

76
interface RetryOptions {
87
maxAttempts: number
@@ -28,12 +27,15 @@ export interface StudioServerOptions {
2827
studioPath: string
2928
projectSlug?: string
3029
cloudApi: StudioCloudApi
30+
betterSqlite3Path: string
3131
}
3232

3333
export interface StudioServerShape {
3434
initializeRoutes(router: Router): void
3535
canAccessStudioAI(browser: Cypress.Browser): Promise<boolean>
36-
setProtocolDb(database: Database.Database): void
36+
setProtocolDbPath(path: string): void
37+
initializeStudioAI(): Promise<void>
38+
destroy(): Promise<void>
3739
}
3840

3941
export interface StudioServerDefaultShape {

scripts/binary/binary-cleanup.js

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ const getDependencyPathsToKeep = async (buildAppDir) => {
5959
'node_modules/string-width/index.js',
6060
'node_modules/proxy-from-env/index.js',
6161
// end needed deps for geckodriver
62+
// better-sqlite3 is needed to be loaded in dynamically in studio
63+
'node_modules/better-sqlite3/lib/index.js',
6264
]
6365

6466
let entryPoints = new Set([

0 commit comments

Comments
 (0)