Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/browser-preview/src/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ export class PreviewBrowserProvider implements BrowserProvider {
}

async openPage(_sessionId: string, url: string): Promise<void> {
if (this.open) {
return
}
this.open = true
if (!this.project.browser) {
throw new Error('Browser is not initialized')
Expand Down
21 changes: 16 additions & 5 deletions packages/browser/src/node/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,38 @@ export function setupBrowserRpc(globalServer: ParentBrowserProject, defaultMocke

const sessions = vitest._browserSessions

let connect = true

if (!sessions.sessionIds.has(sessionId)) {
const ids = [...sessions.sessionIds].join(', ')
return error(
console.warn([
`[vitest] Unknown session id "${sessionId}". Expected one of ${ids}.`,
'Close old browser instances/tabs',
].join('\n'))
/* return error(
new Error(`[vitest] Unknown session id "${sessionId}". Expected one of ${ids}.`),
)
) */
connect = false
}

if (type === 'orchestrator') {
if (connect && type === 'orchestrator') {
const session = sessions.getSession(sessionId)
// it's possible the session was already resolved by the preview provider
session?.connected()
}

const project = vitest.getProjectByName(projectName)
const project = connect ? vitest.getProjectByName(projectName) : undefined

if (!project) {
if (connect && !project) {
return error(
new Error(`[vitest] Project "${projectName}" not found.`),
)
}

if (!project) {
return
}

wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request)

Expand Down
15 changes: 15 additions & 0 deletions packages/vitest/src/node/browser/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ export class BrowserSessions {
}
}

getPreviewProviderSessions(project: TestProject): string | undefined {
if (project.config.browser.provider?.name !== 'preview') {
return undefined
}

const name = project.config.browser.name
for (const [sessionId, session] of this.sessions.entries()) {
if (session.project.config.browser.name === name) {
return sessionId
}
}

return undefined
}

destroySession(sessionId: string): void {
this.sessions.delete(sessionId)
}
Expand Down
79 changes: 65 additions & 14 deletions packages/vitest/src/node/pools/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export function createBrowserPool(vitest: Vitest): ProcessPool {

const projectPools = new WeakMap<TestProject, BrowserPool>()

const ensurePool = (project: TestProject) => {
const ensurePool = (project: TestProject): [existing: boolean, pool: BrowserPool] => {
if (projectPools.has(project)) {
return projectPools.get(project)!
return [true, projectPools.get(project)!]
}

debug?.('creating pool for project %s', project.name)
Expand All @@ -55,7 +55,7 @@ export function createBrowserPool(vitest: Vitest): ProcessPool {
pool.cancel()
})

return pool
return [false, pool]
}

const runWorkspaceTests = async (method: 'run' | 'collect', specs: TestSpecification[]) => {
Expand Down Expand Up @@ -87,14 +87,23 @@ export function createBrowserPool(vitest: Vitest): ProcessPool {

debug?.('provider is ready for %s project', project.name)

const pool = ensurePool(project)
const [existing, pool] = ensurePool(project)
// eager session creation and registration: don't block execution
vitest.state.clearFiles(project, files.map(f => f.filepath))
providers.add(project.browser!.provider)

const prepareSession: Promise<void> | undefined = project.browser!.provider.name === 'preview'
? !existing ? pool.prepareSession() : undefined // <== DON'T RECREATE THE SESSION => Vitest will hang
: undefined

return {
pool,
prepareSession,
provider: project.browser!.provider,
runTests: () => pool.runTests(method, files),
runTests: prepareSession
// run tests once the browser is ready: the client orchestrator connects via RPC
? () => prepareSession.then(() => pool.runTests(method, files))
: () => pool.runTests(method, files),
}
}))

Expand All @@ -111,11 +120,12 @@ export function createBrowserPool(vitest: Vitest): ProcessPool {
return
}

// preview cannot run tests in parallel, but we can launch the browser instances in parallel
if (pool.provider.name === 'preview' || (pool.provider.mocker && pool.provider.supportsParallelism)) {
if (pool.provider.mocker && pool.provider.supportsParallelism) {
parallelPools.push(pool.runTests)
}
else {
// launch browsers instances if required
await pool.pool.launchPreviewProvider()
nonParallelPools.push(pool.runTests)
}
}
Expand Down Expand Up @@ -199,6 +209,23 @@ class BrowserPool {
return this.project.browser!.state.orchestrators
}

prepareSession(): Promise<void> {
const sessionId = crypto.randomUUID()
this.project.vitest._browserSessions.sessionIds.add(sessionId)
return this.project.vitest._browserSessions.createSession(
sessionId,
this.project,
this,
)
}

launchPreviewProvider(): Promise<void> | undefined {
const sessionId = this.project.vitest._browserSessions.getPreviewProviderSessions(this.project)
if (sessionId) {
return this.openPage(sessionId)
}
}

async runTests(method: 'run' | 'collect', files: FileSpecification[]): Promise<void> {
this._promise ??= createDefer<void>()

Expand All @@ -212,18 +239,35 @@ class BrowserPool {

this._queue.push(...files)

const testRun = this.readySessions.size > 0 && this._queue.length > 0

this.readySessions.forEach((sessionId) => {
if (this._queue.length) {
this.readySessions.delete(sessionId)
this.runNextTest(method, sessionId)
}
})

if (this.orchestrators.size >= this.options.maxWorkers) {
if (this.project.browser!.provider.name !== 'preview' && this.orchestrators.size >= this.options.maxWorkers) {
debug?.('all orchestrators are ready, not creating more')
return this._promise
}

if (this.project.browser!.provider.name === 'preview') {
if (testRun) {
return this._promise
}
const sessionId = this.project.vitest._browserSessions.findSessionByBrowser(this.project)
if (sessionId) {
this.runNextTest(method, sessionId)
debug?.('all sessions are created')
return this._promise
}
this._promise.reject(new Error('Preview session not found when starting tests.'))
this.cancel()
return this._promise
}

// open the minimum amount of tabs
// if there is only 1 file running, we don't need 8 tabs running
const workerCount = Math.min(
Expand All @@ -249,19 +293,26 @@ class BrowserPool {
}

private async openPage(sessionId: string) {
const sessionPromise = this.project.vitest._browserSessions.createSession(
sessionId,
this.project,
this,
)
const browser = this.project.browser!
const url = new URL('/__vitest_test__/', this.options.origin)
url.searchParams.set('sessionId', sessionId)
const pagePromise = browser.provider.openPage(
sessionId,
url.toString(),
)
await Promise.all([sessionPromise, pagePromise])
if (browser.provider.name === 'preview') {
await pagePromise
}
else {
await Promise.all([
this.project.vitest._browserSessions.createSession(
sessionId,
this.project,
this,
),
pagePromise,
])
}
}

private getOrchestrator(sessionId: string) {
Expand Down
Loading