Skip to content

Commit

Permalink
allow to keep browser sessions around
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-bromann committed Feb 4, 2024
1 parent 46e857a commit 43750bc
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 26 deletions.
19 changes: 19 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ViteServer } from '../server.js'
import { run } from '../runner.js'
import type { RunnerArgs } from '../types.js'

export async function runCommand (script: string, args: RunnerArgs) {
const server = new ViteServer({ root: args.rootDir })

try {
const env = await server.start(script)
await run(env, args)
} catch (err) {
console.error('Error:', (err as Error).message)
process.exit(1)
} finally {
await server.stop()
}
}

export * from './session.js'
28 changes: 28 additions & 0 deletions src/commands/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { initBrowserSession } from '../utils.js'
import { SessionManager } from '../session.js'
import type { RunnerArgs } from '../types.js'

export async function startSession (args: RunnerArgs) {
const sessionName = args.sessionName
if (!sessionName) {
throw new Error('Please provide a session name')
}

const browser = await initBrowserSession(args)
await SessionManager.saveSession(browser, sessionName)
console.log(`Session "${sessionName}" started, you can now run scripts faster e.g. \`npx exweb ./script.js --sessionName ${sessionName}\``)
process.exit(0)
}

export async function stopSession (args: RunnerArgs) {
const sessionName = args.sessionName
if (!sessionName) {
throw new Error('Please provide a session name')
}

const browser = await SessionManager.loadSession(sessionName)
await browser.deleteSession()

console.log(`Session "${sessionName}" stopped`)
process.exit(0)
}
3 changes: 2 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ export const CLI_OPTIONS = {
browserName: { type: 'string', alias: 'b', default: 'chrome' },
browserVersion: { type: 'string', alias: 'v' },
headless: { type: 'boolean', alias: 'h', default: IS_CI },
rootDir: { type: 'string', alias: 'r', default: process.cwd() }
rootDir: { type: 'string', alias: 'r', default: process.cwd() },
sessionName: { type: 'string', alias: 's' }
} as const

export const PARSE_OPTIONS = {
Expand Down
23 changes: 9 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { parseArgs } from 'node:util'

import { ViteServer } from './server.js'
import { run } from './runner.js'
import { parseFileName } from './utils.js'
import { CLI_OPTIONS, PARSE_OPTIONS } from './constants.js'
import { runCommand, startSession, stopSession } from './commands/index.js'
import type { RunnerArgs } from './types.js'

export default async function cli () {
Expand All @@ -21,17 +20,13 @@ export default async function cli () {
}
})

const args = values as RunnerArgs
const filename = parseFileName(positionals[0])
const server = new ViteServer({ root: args.rootDir })

try {
const env = await server.start(filename)
await run(env, args)
} catch (err) {
console.error('Error:', (err as Error).message)
process.exit(1)
} finally {
await server.stop()
if (positionals.includes('session')) {
if (positionals.includes('stop')) {
return stopSession(values as RunnerArgs)
}

return startSession(values as RunnerArgs)
}

return runCommand(parseFileName(positionals[0]), values as RunnerArgs)
}
28 changes: 17 additions & 11 deletions src/runner.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { remote } from 'webdriverio'

import { getHeadlessArgs } from './utils.js'
import { initBrowserSession } from './utils.js'
import { SessionManager } from './session.js'
import type { ExecutionEnvironment, RunnerArgs } from './types.js'

export async function run (env: ExecutionEnvironment, args: RunnerArgs) {
const browser = await remote({
logLevel: 'error',
capabilities: Object.assign({
browserName: args.browserName,
browserVersion: args.browserVersion
}, getHeadlessArgs(args))
})
const browser = args.sessionName
? await SessionManager.loadSession(args.sessionName)
: await initBrowserSession(args)

let error: Error | undefined

/**
* don't use `await` so that we can trigger url load and listen on events
* at the same time
*/
browser.url(env.url)
await new Promise<void>((resolve) => {
env.server.ws.on('bx:event', (message) => {
Expand All @@ -26,7 +26,13 @@ export async function run (env: ExecutionEnvironment, args: RunnerArgs) {
}
})
})
await browser.deleteSession()

/**
* keep browser session around if a session name is provided
*/
if (!args.sessionName) {
await browser.deleteSession()
}

if (error) {
console.error(error);
Expand Down
44 changes: 44 additions & 0 deletions src/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os from 'node:os'
import path from 'node:path'
import fs from 'node:fs/promises'

import { attach } from 'webdriverio'
import type { Options } from '@wdio/types'

const SESSION_DIR = process.env.TMPDIR || os.tmpdir()

function getSessionFilePath(sessionName: string) {
return path.join(SESSION_DIR, `${sessionName}.json`)
}

interface SessionFile {
capabilities: WebdriverIO.Capabilities
requestedCapabilities: WebdriverIO.Capabilities
sessionId: string
options: Options.WebdriverIO
}

/**
* @description To allow run scripts faster, user can keep a browser session around.
* This class is responsible for managing the session.
*/
export class SessionManager {
static async loadSession(sessionName: string) {
const sessionFilePath = getSessionFilePath(sessionName)
const sessionExists = await fs.access(sessionFilePath).then(() => true, () => false)
if (!sessionExists) {
throw new Error(`Session "${sessionName}" not found`)
}

const session: SessionFile = JSON.parse(await fs.readFile(sessionFilePath, 'utf8'))
return attach(session)
}

static async saveSession(browser: WebdriverIO.Browser, sessionName: string) {
const { capabilities, requestedCapabilities, sessionId, options } = browser
await fs.writeFile(
getSessionFilePath(sessionName),
JSON.stringify({ capabilities, requestedCapabilities, sessionId, options } as SessionFile)
)
}
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface RunnerArgs {
browserVersion?: string
headless: boolean
rootDir: string
sessionName?: string
}

export interface ExecutionEnvironment {
Expand Down
11 changes: 11 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from 'node:path'
import { remote } from 'webdriverio'

import { SUPPORTED_FILE_EXTENSIONS } from './constants.js'
import type { RunnerArgs } from './types.js'
Expand Down Expand Up @@ -40,3 +41,13 @@ export function parseFileName (filename: string) {
}
return path.resolve(process.cwd(), filename)
}

export async function initBrowserSession (args: RunnerArgs) {
return remote({
logLevel: 'error',
capabilities: Object.assign({
browserName: args.browserName,
browserVersion: args.browserVersion
}, getHeadlessArgs(args))
})
}

0 comments on commit 43750bc

Please sign in to comment.