@vostride/agent-qa-plugin-browserstack 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +105 -0
- package/NOTICE.md +11 -0
- package/README.md +249 -2
- package/dist/main.js +7 -0
- package/dist/main.js.map +1 -0
- package/dist/options.schema.json +60 -0
- package/dist/plugin-fBvi25yB.js +954 -0
- package/dist/plugin-fBvi25yB.js.map +1 -0
- package/dist/plugin.js +1 -0
- package/dist/settings.schema.json +59 -0
- package/package.json +79 -4
- package/skill/SKILL.md +165 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-fBvi25yB.js","names":[],"sources":["../src/browserstack-api.ts","../src/devices.ts","../src/playwright-versions.ts","../src/check.ts","../src/local.ts","../src/options.ts","../src/upload.ts","../src/web.ts","../src/plugin.ts"],"sourcesContent":["import { PluginError } from '@vostride/agent-qa-plugin-kit'\nimport type { BrowserStackOptions } from './options.js'\n\nexport type BrowserStackFailureKind = 'auth' | 'capacity' | 'refused' | 'unreachable'\n\nexport interface BrowserStackApiInput {\n apiUrl: string\n appApiUrl: string\n username: string\n accessKey: string\n}\n\nexport interface AppSessionMark {\n status: 'passed' | 'failed'\n reason?: string\n}\n\nexport interface BrowserStackApi {\n appSession(id: string, signal?: AbortSignal): Promise<unknown>\n markAppSession(id: string, mark: AppSessionMark, signal?: AbortSignal): Promise<void>\n runningBuilds(signal?: AbortSignal): Promise<unknown>\n runningBuildSessions(buildId: string, signal?: AbortSignal): Promise<unknown>\n webSession(id: string, signal?: AbortSignal): Promise<unknown>\n markWebSession(id: string, mark: AppSessionMark, signal?: AbortSignal): Promise<void>\n // Both plans answer undefined on 404: that product is not on the account.\n automatePlan(signal?: AbortSignal): Promise<unknown>\n appAutomatePlan(signal?: AbortSignal): Promise<unknown>\n recentApps(customId: string, signal?: AbortSignal): Promise<unknown>\n uploadApp(form: FormData, signal?: AbortSignal): Promise<unknown>\n}\n\nexport function browserStackFailureKind(status: number | undefined): BrowserStackFailureKind {\n if (status === undefined) return 'unreachable'\n if (status === 401) return 'auth'\n if (status === 429) return 'capacity'\n return 'refused'\n}\n\nconst MAX_REASON_LENGTH = 200\n\nfunction oneLine(text: string): string {\n const line = text.replace(/\\s+/g, ' ').trim()\n return line.length > MAX_REASON_LENGTH ? `${line.slice(0, MAX_REASON_LENGTH)}…` : line\n}\n\nfunction vendorMessage(body: unknown): string | undefined {\n if (typeof body !== 'object' || body === null) return undefined\n for (const field of ['message', 'error'] as const) {\n if (field in body) {\n const value = (body as Record<string, unknown>)[field]\n if (typeof value === 'string' && value.trim() !== '') return oneLine(value)\n }\n }\n return undefined\n}\n\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text) as unknown\n } catch {\n return undefined\n }\n}\n\nasync function statusFailure(response: Response, request: string): Promise<PluginError> {\n const text = await response.text().catch(() => '')\n const message = vendorMessage(parseJson(text))\n const reason = message === undefined ? '' : `: ${message}`\n const retryAfter = response.headers.get('retry-after')\n const wait = response.status === 429 && retryAfter !== null ? `, retry after ${retryAfter} s` : ''\n return new PluginError(`BrowserStack answered ${response.status} to ${request}${reason}${wait}`, {\n kind: browserStackFailureKind(response.status),\n })\n}\n\nfunction causeText(error: unknown): string {\n if (!(error instanceof Error)) return oneLine(String(error))\n const cause = error.cause\n if (cause instanceof Error) {\n const code = 'code' in cause && typeof cause.code === 'string' ? cause.code : cause.message\n return oneLine(code)\n }\n return oneLine(error.message)\n}\n\nfunction withSlash(url: string): string {\n return url.endsWith('/') ? url : `${url}/`\n}\n\ninterface SendInit {\n base?: string\n headers?: Record<string, string>\n body?: string | FormData\n signal?: AbortSignal | undefined\n}\n\nexport function createBrowserStackApi(input: BrowserStackApiInput): BrowserStackApi {\n const appBase = withSlash(input.appApiUrl)\n const automateBase = withSlash(input.apiUrl)\n const token = Buffer.from(`${input.username}:${input.accessKey}`).toString('base64')\n const authorization = `Basic ${token}`\n\n const send = async (method: string, path: string, init: SendInit): Promise<Response> => {\n const url = new URL(path, init.base ?? appBase)\n try {\n return await fetch(url, {\n method,\n headers: { accept: 'application/json', ...init.headers, authorization },\n ...(init.body === undefined ? {} : { body: init.body }),\n ...(init.signal === undefined ? {} : { signal: init.signal }),\n })\n } catch (error) {\n if (init.signal?.aborted === true) throw error\n throw new PluginError(\n `BrowserStack could not be reached at ${url.origin}: ${causeText(error)}`,\n { kind: 'unreachable' },\n )\n }\n }\n\n const readJson = async (response: Response, request: string): Promise<unknown> => {\n if (!response.ok) throw await statusFailure(response, request)\n return parseJson(await response.text())\n }\n\n const sessionPath = (id: string) => `app-automate/sessions/${encodeURIComponent(id)}.json`\n const webSessionPath = (id: string) => `automate/sessions/${encodeURIComponent(id)}.json`\n\n const mark = async (\n base: string,\n path: string,\n request: string,\n body: AppSessionMark,\n signal?: AbortSignal,\n ) => {\n const response = await send('PUT', path, {\n base,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n signal,\n })\n if (!response.ok) throw await statusFailure(response, request)\n await response.body?.cancel()\n }\n\n const plan = async (base: string, path: string, signal?: AbortSignal) => {\n const response = await send('GET', path, { base, signal })\n if (response.status === 404) {\n await response.body?.cancel()\n return undefined\n }\n return readJson(response, `GET /${path}`)\n }\n\n return {\n async appSession(id, signal) {\n const response = await send('GET', sessionPath(id), { signal })\n return readJson(response, 'GET /app-automate/sessions/{id}.json')\n },\n\n markAppSession(id, body, signal) {\n const request = 'PUT /app-automate/sessions/{id}.json'\n return mark(appBase, sessionPath(id), request, body, signal)\n },\n\n async runningBuilds(signal) {\n const path = 'automate/builds.json?limit=100&status=running'\n const response = await send('GET', path, { base: automateBase, signal })\n return readJson(response, 'GET /automate/builds.json')\n },\n\n async runningBuildSessions(buildId, signal) {\n const build = encodeURIComponent(buildId)\n const path = `automate/builds/${build}/sessions.json?limit=100&status=running`\n const response = await send('GET', path, { base: automateBase, signal })\n return readJson(response, 'GET /automate/builds/{id}/sessions.json')\n },\n\n async webSession(id, signal) {\n const response = await send('GET', webSessionPath(id), { base: automateBase, signal })\n return readJson(response, 'GET /automate/sessions/{id}.json')\n },\n\n markWebSession(id, body, signal) {\n const request = 'PUT /automate/sessions/{id}.json'\n return mark(automateBase, webSessionPath(id), request, body, signal)\n },\n\n automatePlan(signal) {\n return plan(automateBase, 'automate/plan.json', signal)\n },\n\n appAutomatePlan(signal) {\n return plan(appBase, 'app-automate/plan.json', signal)\n },\n\n async recentApps(customId, signal) {\n const path = `app-automate/recent_apps/${encodeURIComponent(customId)}`\n const response = await send('GET', path, { signal })\n // An unknown custom id may answer 404 with a message, which means nothing is uploaded yet.\n if (response.status === 404) {\n await response.body?.cancel()\n return undefined\n }\n return readJson(response, 'GET /app-automate/recent_apps/{custom_id}')\n },\n\n async uploadApp(form, signal) {\n const response = await send('POST', 'app-automate/upload', { body: form, signal })\n return readJson(response, 'POST /app-automate/upload')\n },\n }\n}\n\nexport function browserStackApiFor(options: BrowserStackOptions): BrowserStackApi {\n return createBrowserStackApi({\n apiUrl: options.apiUrl,\n appApiUrl: options.appApiUrl,\n username: options.username,\n accessKey: options.accessKey,\n })\n}\n","import { randomUUID } from 'node:crypto'\nimport { z } from '@vostride/agent-qa-plugin-kit'\nimport { browserStackApiFor, type AppSessionMark } from './browserstack-api.js'\nimport type { BrowserStackOptions, BrowserStackSettings } from './options.js'\n\ntype Json = z.core.util.JSONType\n\n// The newest Appium BrowserStack offers on its devices; it moves with each plugin release.\nexport const DEFAULT_APPIUM_VERSION = '3.5.2'\n\nconst MAX_MARK_REASON_LENGTH = 255\n\nexport const DeviceHandleSchema = z.object({\n kind: z.literal('device'),\n id: z.string().min(1),\n platform: z.enum(['android', 'ios']),\n appiumVersion: z.string().min(1),\n})\n\nexport type DeviceHandle = z.output<typeof DeviceHandleSchema>\n\nexport interface DeviceRequest {\n options: BrowserStackOptions\n settings: BrowserStackSettings\n platform: 'android' | 'ios'\n capabilities: Record<string, unknown>\n testName: string | undefined\n runId: string | undefined\n localIdentifier: string | undefined\n}\n\nexport interface DeviceAnswer {\n handle: Json\n protocol: 'appium'\n url: string\n capabilities: Record<string, unknown>\n mask: string[]\n}\n\nfunction ownAppiumVersion(capabilities: Record<string, unknown>): string | undefined {\n const own = capabilities['bstack:options']\n if (typeof own !== 'object' || own === null || !('appiumVersion' in own)) return undefined\n const { appiumVersion } = own\n return typeof appiumVersion === 'string' && appiumVersion !== '' ? appiumVersion : undefined\n}\n\nexport function deviceAnswer(request: DeviceRequest): DeviceAnswer {\n const { options, settings } = request\n const appiumVersion =\n ownAppiumVersion(request.capabilities) ?? settings.appiumVersion ?? DEFAULT_APPIUM_VERSION\n const build = settings.build ?? request.runId\n const name = request.testName\n return {\n handle: {\n kind: 'device',\n id: randomUUID(),\n platform: request.platform,\n appiumVersion,\n ...(build === undefined ? {} : { build }),\n ...(name === undefined ? {} : { name }),\n },\n protocol: 'appium',\n url: options.hubUrl,\n capabilities: {\n 'bstack:options': {\n userName: options.username,\n accessKey: options.accessKey,\n appiumVersion,\n projectName: settings.project,\n ...(build === undefined ? {} : { buildName: build }),\n ...(name === undefined ? {} : { sessionName: name }),\n ...(request.localIdentifier === undefined\n ? {}\n : { local: true, localIdentifier: request.localIdentifier }),\n ...settings.extra,\n },\n },\n mask: [],\n }\n}\n\nexport function sessionDetails(body: unknown): Record<string, unknown> | undefined {\n if (typeof body !== 'object' || body === null) return undefined\n const wrapped = (body as Record<string, unknown>).automation_session\n if (typeof wrapped === 'object' && wrapped !== null) return wrapped as Record<string, unknown>\n return body as Record<string, unknown>\n}\n\nexport function warn(line: string): void {\n process.stderr.write(`${line.replace(/\\s+/g, ' ').trim()}\\n`)\n}\n\nexport function failureText(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nexport interface DeviceStartedInput {\n options: BrowserStackOptions\n handle: DeviceHandle\n sessionId: string | undefined\n signal: AbortSignal\n}\n\nexport async function deviceStarted(\n input: DeviceStartedInput,\n): Promise<{ link?: string; appiumVersion: string }> {\n const { appiumVersion } = input.handle\n if (input.sessionId === undefined) return { appiumVersion }\n try {\n const details = sessionDetails(\n await browserStackApiFor(input.options).appSession(input.sessionId, input.signal),\n )\n // browser_url needs a BrowserStack login; public_url is shareable, so it is never recorded.\n const link = details?.browser_url\n if (typeof link === 'string' && link !== '') return { link, appiumVersion }\n warn(`BrowserStack: App Automate session ${input.sessionId} has no dashboard link.`)\n } catch (error) {\n warn(\n `BrowserStack: could not read App Automate session ${input.sessionId}: ${failureText(error)}`,\n )\n }\n return { appiumVersion }\n}\n\nexport type SessionOutcome = 'passed' | 'failed' | 'error' | 'cancelled'\n\nexport function sessionMark(\n outcome: SessionOutcome,\n reason: string | undefined,\n): AppSessionMark | undefined {\n if (outcome === 'cancelled') return undefined\n if (outcome === 'passed') return { status: 'passed' }\n return {\n status: 'failed',\n ...(reason === undefined ? {} : { reason: reason.slice(0, MAX_MARK_REASON_LENGTH) }),\n }\n}\n\nexport interface MarkAppSessionInput {\n options: BrowserStackOptions\n sessionId: string\n outcome: SessionOutcome\n reason: string | undefined\n signal: AbortSignal\n}\n\nexport async function markAppSession(input: MarkAppSessionInput): Promise<void> {\n const mark = sessionMark(input.outcome, input.reason)\n if (mark === undefined) return\n try {\n await browserStackApiFor(input.options).markAppSession(input.sessionId, mark, input.signal)\n } catch (error) {\n warn(\n `BrowserStack: could not mark App Automate session ${input.sessionId} ${mark.status}: ${failureText(error)}`,\n )\n }\n}\n","// The Playwright versions BrowserStack's browsers-and-OS page lists; it moves with each release.\nexport const BROWSERSTACK_PLAYWRIGHT_VERSIONS = ['1.59', '1.60', '1.61', '1.62', '1.63'] as const\n\nexport function playwrightMajorMinor(version: string): string | undefined {\n const match = /^(\\d+)\\.(\\d+)(?:\\.|$|-)/.exec(version)\n return match === null ? undefined : `${match[1]}.${match[2]}`\n}\n\nexport function listedPlaywright(version: string): boolean {\n const majorMinor = playwrightMajorMinor(version)\n return BROWSERSTACK_PLAYWRIGHT_VERSIONS.some((listed) => listed === majorMinor)\n}\n","import { PluginError, z } from '@vostride/agent-qa-plugin-kit'\nimport { browserStackApiFor } from './browserstack-api.js'\nimport { failureText } from './devices.js'\nimport type { BrowserStackOptions } from './options.js'\nimport {\n BROWSERSTACK_PLAYWRIGHT_VERSIONS,\n listedPlaywright,\n playwrightMajorMinor,\n} from './playwright-versions.js'\n\nconst PlanSchema = z.object({\n parallel_sessions_running: z.number(),\n parallel_sessions_max_allowed: z.number(),\n queued_sessions: z.number(),\n})\n\ntype PlanRead =\n | { kind: 'plan'; line: string }\n | { kind: 'refused' }\n | { kind: 'warn'; line: string }\n\nexport interface CheckAccountInput {\n options: BrowserStackOptions\n playwrightVersion: string\n signal: AbortSignal\n}\n\nasync function readPlan(product: string, read: () => Promise<unknown>): Promise<PlanRead> {\n let answer: unknown\n try {\n answer = await read()\n } catch (error) {\n if (error instanceof PluginError && error.kind === 'auth') return { kind: 'refused' }\n return { kind: 'warn', line: `${product} plan not readable: ${failureText(error)}` }\n }\n if (answer === undefined) return { kind: 'warn', line: `${product} is not on this account` }\n const plan = PlanSchema.safeParse(answer)\n if (!plan.success) {\n return {\n kind: 'warn',\n line: `${product} plan not readable: BrowserStack answered another shape`,\n }\n }\n const { parallel_sessions_running: running, parallel_sessions_max_allowed: allowed } = plan.data\n const queued = plan.data.queued_sessions\n return {\n kind: 'plan',\n line: `${product}: ${String(running)} of ${String(allowed)} parallel sessions running, ${String(queued)} queued`,\n }\n}\n\nfunction playwrightLine(version: string): string | undefined {\n if (listedPlaywright(version)) return undefined\n const first = BROWSERSTACK_PLAYWRIGHT_VERSIONS[0]\n const last = BROWSERSTACK_PLAYWRIGHT_VERSIONS[BROWSERSTACK_PLAYWRIGHT_VERSIONS.length - 1]\n const ours = playwrightMajorMinor(version)\n if (ours === undefined) {\n return `BrowserStack lists Playwright ${first} to ${last}, and agent-qa's Playwright version is unknown; web tests on BrowserStack are refused until it is known.`\n }\n return `BrowserStack lists Playwright ${first} to ${last}, not ${ours}; web tests on BrowserStack are refused until it does.`\n}\n\n// Reads only the two plan endpoints, so checking an account never opens a session.\nexport async function checkAccount(\n input: CheckAccountInput,\n): Promise<{ state: 'ok' | 'warn'; lines: string[] }> {\n const api = browserStackApiFor(input.options)\n const reads = await Promise.all([\n readPlan('Automate', () => api.automatePlan(input.signal)),\n readPlan('App Automate', () => api.appAutomatePlan(input.signal)),\n ])\n const version = playwrightLine(input.playwrightVersion)\n const extra = version === undefined ? [] : [version]\n if (reads.some((read) => read.kind === 'refused')) {\n return { state: 'warn', lines: ['the username or access key was refused', ...extra] }\n }\n const lines = reads.flatMap((read) => (read.kind === 'refused' ? [] : [read.line]))\n const warned = extra.length > 0 || reads.some((read) => read.kind === 'warn')\n return { state: warned ? 'warn' : 'ok', lines: [...lines, ...extra] }\n}\n","import { execFile, spawnSync, type ChildProcess } from 'node:child_process'\nimport { randomBytes } from 'node:crypto'\nimport { writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { format } from 'node:util'\nimport { PluginError, type PluginProgress } from '@vostride/agent-qa-plugin-kit'\nimport { Local } from 'browserstack-local'\n\nconst STOP_BOUND_MS = 3_000\nconst CHECK_BOUND_MS = 5_000\n\nexport interface TunnelRequest {\n accessKey: string\n identifier: string\n binary: string | undefined\n}\n\nexport interface RunningTunnel {\n stop(): Promise<void>\n}\n\n// A start still in flight can be aborted: the plugin may stop before the tunnel connects.\nexport interface StartingTunnel {\n running: Promise<RunningTunnel>\n abort(): Promise<void>\n}\n\nexport interface TunnelFactory {\n start(request: TunnelRequest): StartingTunnel\n}\n\ninterface WrapperState {\n retriesLeft: number\n tunnel?: ChildProcess\n binaryPath?: string\n key?: string\n getBinaryPath(found: (path: string | undefined) => void): void\n}\n\n// macOS's EBADARCH, which Node names only \"Unknown system error -86\".\nconst BAD_CPU_TYPE = -86\n\n// browserstack-local downloads BrowserStack's Intel build on every Mac, and only a Mac has Rosetta.\nexport function cannotRunReason(\n binary: string,\n error: NodeJS.ErrnoException,\n platform: NodeJS.Platform,\n): string {\n if (platform === 'darwin' && error.errno === BAD_CPU_TYPE) {\n return `this machine cannot run ${binary} (EBADARCH: bad CPU type in executable). Install Rosetta (softwareupdate --install-rosetta --agree-to-license) or set localBinary to a binary built for this Mac`\n }\n const code = error.code ?? error.message\n if (code === 'ENOENT') {\n return `${binary} does not exist (ENOENT). Set localBinary to the path of a BrowserStackLocal binary`\n }\n if (code === 'EACCES') {\n return `this machine cannot run ${binary} (EACCES). Check that it is an executable file, or set localBinary to one that is`\n }\n return `this machine cannot run ${binary} (${code}). Set localBinary to a binary this machine can run`\n}\n\nfunction wrapperBinary(wrapper: WrapperState, accessKey: string): Promise<string> {\n // A download authenticates with the key, as the wrapper's own start would set it.\n wrapper.key = accessKey\n return new Promise<string>((resolve, reject) => {\n wrapper.getBinaryPath((path) => {\n if (path) {\n resolve(path)\n return\n }\n reject(\n new Error('browserstack-local found no BrowserStack Local binary and could not download one'),\n )\n })\n })\n}\n\n// spawnSync returns a spawn failure, such as BrowserStack's Intel build on an Apple Silicon Mac\n// without Rosetta, where the wrapper's own launch throws it from a callback no caller can catch.\nfunction cannotRun(binary: string): NodeJS.ErrnoException | undefined {\n const { error } = spawnSync(binary, ['--version'], { stdio: 'ignore', timeout: CHECK_BOUND_MS })\n if (error === undefined) return undefined\n return (error as NodeJS.ErrnoException).code === 'ETIMEDOUT' ? undefined : error\n}\n\n// The binary's own stop for a daemon by identifier; it runs whatever state the launch reached.\nfunction stopDaemon(binary: string, request: TunnelRequest): Promise<void> {\n const args = ['--daemon', 'stop', '--local-identifier', request.identifier]\n return new Promise<void>((done) => {\n execFile(binary, [...args, '--key', request.accessKey], { timeout: STOP_BOUND_MS }, () =>\n done(),\n )\n })\n}\n\nconst CONSOLE_METHODS = ['log', 'info', 'debug', 'warn', 'error'] as const\n\n// The wrapper logs the binary's whole command line, --key included, when the binary fails.\nfunction maskConsole(secret: string): () => void {\n if (secret === '') return () => {}\n const originals = CONSOLE_METHODS.map((method) => console[method])\n CONSOLE_METHODS.forEach((method, index) => {\n const original = originals[index] as (...args: unknown[]) => void\n console[method] = (...args: unknown[]) => original(format(...args).split(secret).join('***'))\n })\n return () => {\n CONSOLE_METHODS.forEach((method, index) => {\n console[method] = originals[index] as (...args: unknown[]) => void\n })\n }\n}\n\nfunction localLogFile(): string {\n const name = `agent-qa-browserstack-local-${process.pid}-${randomBytes(4).toString('hex')}.log`\n return join(tmpdir(), name)\n}\n\nexport const browserStackLocalFactory: TunnelFactory = {\n start(request) {\n const local = new Local()\n const wrapper = local as unknown as WrapperState\n // After a failed launch the wrapper deletes the binary and launches a fresh download unchecked,\n // which can throw where nothing catches it. The next acquire tries again instead.\n wrapper.retriesLeft = 0\n const unmask = maskConsole(request.accessKey)\n let aborted = false\n // The launch waits for the check, so it always runs from this promise and nothing it throws\n // escapes.\n const running = (async (): Promise<RunningTunnel> => {\n const binary = request.binary ?? (await wrapperBinary(wrapper, request.accessKey))\n const failure = cannotRun(binary)\n if (failure !== undefined) throw new Error(cannotRunReason(binary, failure, process.platform))\n if (aborted) throw new Error('it was stopped while it was starting')\n const logFile = localLogFile()\n // Created private first; the wrapper only truncates it, so the mode holds.\n writeFileSync(logFile, '', { mode: 0o600 })\n return new Promise<RunningTunnel>((resolve, reject) => {\n local.start(\n {\n key: request.accessKey,\n localIdentifier: request.identifier,\n logFile,\n binarypath: binary,\n },\n (error) => {\n if (error) {\n const [first] = error.message.split('\\n')\n reject(new Error(`${first} (its log is ${logFile})`))\n return\n }\n resolve({ stop: () => new Promise<void>((done) => local.stop(() => done())) })\n },\n )\n })\n })().finally(unmask)\n return {\n running,\n async abort() {\n aborted = true\n wrapper.tunnel?.kill()\n if (wrapper.binaryPath !== undefined) await stopDaemon(wrapper.binaryPath, request)\n },\n }\n },\n}\n\nexport interface TunnelManagerInput {\n factory: TunnelFactory\n accessKey: string\n local: boolean\n localIdentifier: string | undefined\n localBinary: string | undefined\n}\n\nexport interface TunnelManager {\n readonly identifier: string | undefined\n ensure(signal: AbortSignal, progress: (update: PluginProgress) => void): Promise<void>\n stop(): Promise<void>\n}\n\nfunction freshLocalIdentifier(): string {\n return `agent-qa-${randomBytes(8).toString('hex')}`\n}\n\n// The tunnel starts from acquire, where a bare path would name a remote setting.\nfunction startFailure(error: unknown, accessKey: string, binarySet: boolean): PluginError {\n if (error instanceof PluginError) return error\n const text = error instanceof Error ? error.message : String(error)\n const shown = accessKey === '' ? text : text.split(accessKey).join('***')\n return new PluginError(`BrowserStack Local did not start: ${shown}`, {\n kind: 'unreachable',\n path: binarySet ? 'options.localBinary' : 'options.local',\n })\n}\n\nfunction bounded(work: Promise<void>, boundMs: number): Promise<void> {\n let timer: NodeJS.Timeout | undefined\n const bound = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, boundMs)\n })\n return Promise.race([work.catch(() => {}), bound]).finally(() => clearTimeout(timer))\n}\n\nexport function createTunnelManager(input: TunnelManagerInput): TunnelManager {\n const identifier = input.local\n ? (input.localIdentifier ?? freshLocalIdentifier())\n : input.localIdentifier\n const binarySet = input.localBinary !== undefined\n let starting: StartingTunnel | undefined\n let stopped = false\n let connected = false\n\n const start = (): Promise<RunningTunnel> => {\n if (starting !== undefined) return starting.running\n let begun: StartingTunnel\n try {\n begun = input.factory.start({\n accessKey: input.accessKey,\n identifier: identifier as string,\n binary: input.localBinary,\n })\n } catch (error) {\n return Promise.reject(startFailure(error, input.accessKey, binarySet))\n }\n const attempt: StartingTunnel = {\n running: begun.running.catch((error: unknown) => {\n throw startFailure(error, input.accessKey, binarySet)\n }),\n abort: () => begun.abort(),\n }\n starting = attempt\n // A failed start is not kept, so the next acquire tries again.\n attempt.running.then(\n () => {\n connected = true\n },\n () => {\n if (starting === attempt) starting = undefined\n },\n )\n return attempt.running\n }\n\n return {\n identifier,\n\n async ensure(signal, progress) {\n if (!input.local) return\n if (stopped) {\n throw new PluginError('BrowserStack Local was already stopped for this run.', {\n kind: 'unreachable',\n path: 'options.local',\n })\n }\n signal.throwIfAborted()\n const shared = start()\n if (!connected) progress({ message: 'starting BrowserStack Local' })\n await new Promise<void>((resolve, reject) => {\n const onAbort = () =>\n reject(new Error('the acquire was cancelled while BrowserStack Local was starting'))\n signal.addEventListener('abort', onAbort, { once: true })\n shared.then(\n () => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n },\n (error: unknown) => {\n signal.removeEventListener('abort', onAbort)\n reject(error)\n },\n )\n })\n },\n\n async stop() {\n if (stopped) return\n stopped = true\n const pending = starting\n if (pending === undefined) return\n const stopRunning = pending.running.then((tunnel) => tunnel.stop())\n if (connected) {\n await bounded(stopRunning, STOP_BOUND_MS)\n return\n }\n // Still starting: abort the launch, and stop the tunnel too if it connects meanwhile.\n const both = Promise.allSettled([pending.abort(), stopRunning]).then(() => {})\n await bounded(both, STOP_BOUND_MS)\n },\n }\n}\n","import { credential, PluginError, z } from '@vostride/agent-qa-plugin-kit'\n\nexport const BROWSERSTACK_BROWSERS = [\n 'chrome',\n 'edge',\n 'playwright-chromium',\n 'playwright-firefox',\n 'playwright-webkit',\n] as const\n\nexport const options = z.strictObject({\n username: credential('The BrowserStack username'),\n accessKey: credential('The BrowserStack access key'),\n apiUrl: z\n .url()\n .default('https://api.browserstack.com')\n .describe(\"BrowserStack's Automate API, https:// unless it points at this machine\"),\n appApiUrl: z\n .url()\n .default('https://api-cloud.browserstack.com')\n .describe(\"BrowserStack's App Automate API, https:// unless it points at this machine\"),\n hubUrl: z\n .url()\n .default('https://hub.browserstack.com/wd/hub')\n .describe(\"BrowserStack's Appium hub, https:// unless it points at this machine\"),\n playwrightUrl: z\n .url()\n .default('wss://cdp.browserstack.com/playwright')\n .describe(\"BrowserStack's Playwright endpoint, wss:// unless it points at this machine\"),\n local: z\n .boolean()\n .default(false)\n .describe(\n 'Start a BrowserStack Local tunnel for this run, named by localIdentifier or a fresh one',\n ),\n localIdentifier: z\n .string()\n .min(1)\n .optional()\n .describe(\n 'The identifier of a BrowserStack Local tunnel; without local, a tunnel already running',\n ),\n localBinary: z\n .string()\n .min(1)\n .optional()\n .describe('The BrowserStackLocal binary to run instead of the one the wrapper downloads'),\n})\n\nexport type BrowserStackOptions = z.output<typeof options>\n\nexport const settings = z.strictObject({\n os: z.string().min(1).default('OS X').describe('The operating system, for example Windows'),\n osVersion: z\n .string()\n .min(1)\n .default('Sequoia')\n .describe('The operating system version, for example 11 or Sequoia'),\n browser: z\n .enum(BROWSERSTACK_BROWSERS)\n .optional()\n .describe(\"BrowserStack's browser; by default it follows the test's engine\"),\n browserVersion: z\n .string()\n .min(1)\n .optional()\n .describe('The Chrome or Edge version, for example latest or latest-1'),\n project: z\n .string()\n .min(1)\n .default('agent-qa')\n .describe('The BrowserStack project the sessions are filed under'),\n build: z\n .string()\n .min(1)\n .optional()\n .describe('The BrowserStack build name; by default the run id'),\n appiumVersion: z\n .string()\n .min(1)\n .optional()\n .describe(\"The Appium version device sessions ask for; by default the plugin's own\"),\n extra: z\n .record(z.string(), z.unknown())\n .optional()\n .describe('Capabilities merged last, overriding the ones above'),\n})\n\nexport type BrowserStackSettings = z.output<typeof settings>\n\nconst ENDPOINTS = [\n { path: 'apiUrl', secure: 'https:', plain: 'http:' },\n { path: 'appApiUrl', secure: 'https:', plain: 'http:' },\n { path: 'hubUrl', secure: 'https:', plain: 'http:' },\n { path: 'playwrightUrl', secure: 'wss:', plain: 'ws:' },\n] as const\n\nfunction isLoopback(hostname: string): boolean {\n return (\n hostname === 'localhost' || hostname === '[::1]' || /^127(?:\\.\\d{1,3}){3}$/.test(hostname)\n )\n}\n\nexport function checkLocal(input: BrowserStackOptions): void {\n if (input.localBinary !== undefined && !input.local) {\n throw new PluginError(\n 'localBinary names the tunnel the plugin starts, so it needs local: true.',\n { kind: 'refused', path: 'localBinary' },\n )\n }\n}\n\nexport function checkEndpoints(input: BrowserStackOptions): void {\n for (const endpoint of ENDPOINTS) {\n const url = new URL(input[endpoint.path])\n const allowed =\n url.username === '' &&\n url.password === '' &&\n (url.protocol === endpoint.secure ||\n (url.protocol === endpoint.plain && isLoopback(url.hostname)))\n if (!allowed) {\n const scheme = endpoint.secure.slice(0, -1)\n throw new PluginError(\n `${endpoint.path} must use ${scheme}:// with no user or password, unless it points at this machine.`,\n { kind: 'refused', path: endpoint.path },\n )\n }\n }\n}\n","import { createHash } from 'node:crypto'\nimport { openAsBlob } from 'node:fs'\nimport { basename } from 'node:path'\nimport { PluginError, type PluginProgress } from '@vostride/agent-qa-plugin-kit'\nimport { browserStackApiFor } from './browserstack-api.js'\nimport type { BrowserStackOptions } from './options.js'\n\nconst MEGABYTE = 1024 * 1024\n\nexport function customIdFor(sha256: string): string {\n if (!/^[0-9a-f]{64}$/i.test(sha256)) {\n throw new PluginError(\"sha256 must be the 64 hex digits of the app's SHA-256.\", {\n kind: 'refused',\n path: 'sha256',\n })\n }\n return `agent-qa-${sha256.slice(0, 32).toLowerCase()}`\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction appUrlOf(value: unknown): string | undefined {\n if (!isRecord(value)) return undefined\n const appUrl = value.app_url\n return typeof appUrl === 'string' && appUrl !== '' ? appUrl : undefined\n}\n\nfunction uploadedAt(value: Record<string, unknown>): number {\n const at = typeof value.uploaded_at === 'string' ? Date.parse(value.uploaded_at) : Number.NaN\n return Number.isNaN(at) ? Number.NEGATIVE_INFINITY : at\n}\n\n// Anything but a non-empty list means nothing was uploaded under the custom id yet.\nexport function newestAppUrl(recent: unknown): string | undefined {\n if (!Array.isArray(recent)) return undefined\n const apps = recent.filter(isRecord).filter((app) => appUrlOf(app) !== undefined)\n const newest = [...apps].sort((a, b) => uploadedAt(b) - uploadedAt(a))[0]\n return appUrlOf(newest)\n}\n\nfunction megabytes(size: number): string {\n const amount = size / MEGABYTE\n return amount >= 0.1 ? amount.toFixed(1) : '<0.1'\n}\n\nasync function readApp(path: string): Promise<Blob> {\n try {\n return await openAsBlob(path)\n } catch (error) {\n const code =\n error instanceof Error && 'code' in error && typeof error.code === 'string'\n ? error.code\n : 'unreadable'\n throw new PluginError(`The app at ${path} cannot be read (${code}).`, {\n kind: 'refused',\n path: 'path',\n })\n }\n}\n\n// The upload is keyed by the host's hash, so the bytes sent must be the bytes it hashed. A blob\n// from openAsBlob refuses to read once its file changes, so the checked blob is what is sent.\nasync function checkContent(app: Blob, request: UploadAppRequest): Promise<void> {\n const changed = () =>\n new PluginError(\n `The app at ${request.path} no longer matches the SHA-256 agent-qa computed; run again.`,\n { kind: 'refused', path: 'path' },\n )\n if (app.size !== request.size) throw changed()\n const hash = createHash('sha256')\n try {\n for await (const chunk of app.stream()) {\n request.signal.throwIfAborted()\n hash.update(chunk)\n }\n } catch (error) {\n if (request.signal.aborted) throw error\n throw changed()\n }\n if (hash.digest('hex') !== request.sha256.toLowerCase()) throw changed()\n}\n\nexport interface UploadAppRequest {\n options: BrowserStackOptions\n path: string\n sha256: string\n size: number\n signal: AbortSignal\n progress(update: PluginProgress): void\n}\n\nexport async function uploadApp(request: UploadAppRequest): Promise<{ ref: string }> {\n const customId = customIdFor(request.sha256)\n const api = browserStackApiFor(request.options)\n const reused = newestAppUrl(await api.recentApps(customId, request.signal))\n if (reused !== undefined) return { ref: reused }\n\n const app = await readApp(request.path)\n await checkContent(app, request)\n const form = new FormData()\n form.append('custom_id', customId)\n form.append('file', app, basename(request.path))\n request.progress({ message: `uploading app (${megabytes(request.size)} MB)` })\n const ref = appUrlOf(await api.uploadApp(form, request.signal))\n if (ref === undefined) {\n throw new PluginError('BrowserStack answered the app upload without an app_url.', {\n kind: 'refused',\n })\n }\n request.progress({ message: 'app uploaded' })\n return { ref }\n}\n","import { randomUUID } from 'node:crypto'\nimport { PluginError, z } from '@vostride/agent-qa-plugin-kit'\nimport { browserStackApiFor, type BrowserStackApi } from './browserstack-api.js'\nimport {\n failureText,\n sessionDetails,\n sessionMark,\n warn,\n type SessionOutcome,\n} from './devices.js'\nimport type { BrowserStackOptions, BrowserStackSettings } from './options.js'\nimport {\n BROWSERSTACK_PLAYWRIGHT_VERSIONS,\n listedPlaywright,\n playwrightMajorMinor,\n} from './playwright-versions.js'\n\ntype Json = z.core.util.JSONType\ntype Engine = 'chromium' | 'firefox' | 'webkit'\n\n// BrowserStack's created_at and this machine's clock may disagree by a few seconds.\nconst START_SLACK_MS = 5_000\n\nexport const WebHandleSchema = z.object({\n kind: z.literal('web'),\n id: z.string().min(1),\n build: z.string().optional(),\n name: z.string().optional(),\n startedAt: z.string(),\n})\n\nexport type WebHandle = z.output<typeof WebHandleSchema>\n\nconst ENGINE_BROWSERS = {\n chromium: 'chrome',\n firefox: 'playwright-firefox',\n webkit: 'playwright-webkit',\n} as const\n\nconst BROWSER_ENGINES = {\n chrome: 'chromium',\n edge: 'chromium',\n 'playwright-chromium': 'chromium',\n 'playwright-firefox': 'firefox',\n 'playwright-webkit': 'webkit',\n} as const\n\nexport interface WebRequest {\n options: BrowserStackOptions\n settings: BrowserStackSettings\n engine: Engine\n playwrightVersion: string\n testName: string | undefined\n runId: string | undefined\n localIdentifier: string | undefined\n}\n\nexport interface WebAnswer {\n handle: Json\n protocol: 'playwright'\n url: string\n mask: string[]\n}\n\nexport function checkPlaywrightVersion(version: string): void {\n if (listedPlaywright(version)) return\n const first = BROWSERSTACK_PLAYWRIGHT_VERSIONS[0]\n const last = BROWSERSTACK_PLAYWRIGHT_VERSIONS[BROWSERSTACK_PLAYWRIGHT_VERSIONS.length - 1]\n const ours = playwrightMajorMinor(version)\n const which = ours === undefined ? \"and agent-qa's Playwright version is unknown\" : `not ${ours}`\n throw new PluginError(\n `BrowserStack lists Playwright ${first} to ${last}, ${which}, so web tests cannot run there yet; run this test here or on another remote.`,\n { kind: 'version' },\n )\n}\n\nexport function webCapabilities(request: WebRequest): Record<string, unknown> {\n const { options, settings, engine } = request\n const browser = settings.browser ?? ENGINE_BROWSERS[engine]\n if (BROWSER_ENGINES[browser] !== engine) {\n throw new PluginError(`BrowserStack's ${browser} cannot run a ${engine} test.`, {\n kind: 'refused',\n path: 'settings.browser',\n })\n }\n const build = settings.build ?? request.runId\n return {\n 'browserstack.username': options.username,\n 'browserstack.accessKey': options.accessKey,\n browser,\n os: settings.os,\n os_version: settings.osVersion,\n ...(settings.browserVersion === undefined ? {} : { browser_version: settings.browserVersion }),\n 'client.playwrightVersion': request.playwrightVersion,\n project: settings.project,\n ...(build === undefined ? {} : { build }),\n ...(request.testName === undefined ? {} : { name: request.testName }),\n ...(request.localIdentifier === undefined\n ? {}\n : { 'browserstack.local': true, 'browserstack.localIdentifier': request.localIdentifier }),\n ...settings.extra,\n }\n}\n\nexport function playwrightAnswer(request: WebRequest): WebAnswer {\n checkPlaywrightVersion(request.playwrightVersion)\n const caps = webCapabilities(request)\n const base = request.options.playwrightUrl\n const query = `caps=${encodeURIComponent(JSON.stringify(caps))}`\n const url = `${base}${base.includes('?') ? '&' : '?'}${query}`\n const { build, name } = caps\n return {\n handle: {\n kind: 'web',\n id: randomUUID(),\n ...(typeof build === 'string' ? { build } : {}),\n ...(typeof name === 'string' ? { name } : {}),\n startedAt: new Date().toISOString(),\n },\n protocol: 'playwright',\n url,\n mask: [url],\n }\n}\n\nexport interface WebSession {\n hashedId: string\n link: string | undefined\n}\n\nfunction itemsOf(body: unknown, wrapper: string): Record<string, unknown>[] {\n if (!Array.isArray(body)) return []\n return body.flatMap((item: unknown) => {\n if (typeof item !== 'object' || item === null) return []\n const wrapped = (item as Record<string, unknown>)[wrapper]\n if (typeof wrapped === 'object' && wrapped !== null) return [wrapped as Record<string, unknown>]\n return [item as Record<string, unknown>]\n })\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\nexport interface FindWebSessionInput {\n api: BrowserStackApi\n build: string\n name: string\n startedAt: string\n signal: AbortSignal\n}\n\n// Reads automate/builds.json, each matching build's sessions.json and each candidate's details.\n// The session is found only when exactly one running session in the build carries the test's\n// name and started after the acquire; a guessed session is never linked or marked.\nexport async function findWebSession(input: FindWebSessionInput): Promise<WebSession | undefined> {\n const { api, signal } = input\n const earliest = Date.parse(input.startedAt) - START_SLACK_MS\n const builds = itemsOf(await api.runningBuilds(signal), 'automation_build').filter(\n (build) => build.name === input.build,\n )\n const found: WebSession[] = []\n for (const build of builds) {\n const buildId = text(build.hashed_id)\n if (buildId === undefined) continue\n const sessions = itemsOf(\n await api.runningBuildSessions(buildId, signal),\n 'automation_session',\n ).filter((session) => session.name === input.name)\n for (const session of sessions) {\n const hashedId = text(session.hashed_id)\n if (hashedId === undefined) continue\n const details = sessionDetails(await api.webSession(hashedId, signal))\n const createdAt = Date.parse(text(details?.created_at) ?? '')\n if (!Number.isFinite(createdAt) || createdAt < earliest) continue\n // browser_url needs a BrowserStack login; public_url is shareable, so it is never recorded.\n found.push({ hashedId, link: text(details?.browser_url) ?? text(session.browser_url) })\n }\n }\n return found.length === 1 ? found[0] : undefined\n}\n\nexport interface WebStartedInput {\n options: BrowserStackOptions\n handle: WebHandle\n signal: AbortSignal\n}\n\nexport async function webStarted(\n input: WebStartedInput,\n): Promise<{ session: WebSession | undefined; link?: string }> {\n const { build, name, startedAt } = input.handle\n const unmarked = `BrowserStack: no single web session matched build ${build ?? '(none)'} and name ${name ?? '(none)'}; it stays unmarked.`\n if (build === undefined || name === undefined) {\n warn(unmarked)\n return { session: undefined }\n }\n let session: WebSession | undefined\n try {\n const api = browserStackApiFor(input.options)\n session = await findWebSession({ api, build, name, startedAt, signal: input.signal })\n } catch (error) {\n warn(\n `BrowserStack: could not look up the web session for build ${build} and name ${name}: ${failureText(error)}; it stays unmarked.`,\n )\n return { session: undefined }\n }\n if (session === undefined) {\n warn(unmarked)\n return { session: undefined }\n }\n if (session.link === undefined) {\n warn(`BrowserStack: Automate session ${session.hashedId} has no dashboard link.`)\n return { session }\n }\n return { session, link: session.link }\n}\n\nexport interface MarkWebSessionInput {\n options: BrowserStackOptions\n sessionId: string\n outcome: SessionOutcome\n reason: string | undefined\n signal: AbortSignal\n}\n\nexport async function markWebSession(input: MarkWebSessionInput): Promise<void> {\n const mark = sessionMark(input.outcome, input.reason)\n if (mark === undefined) return\n try {\n // Automate's mark documents reason as required, so a passed mark sends an empty one.\n const body = { ...mark, reason: mark.reason ?? '' }\n await browserStackApiFor(input.options).markWebSession(input.sessionId, body, input.signal)\n } catch (error) {\n warn(\n `BrowserStack: could not mark Automate session ${input.sessionId} ${mark.status}: ${failureText(error)}`,\n )\n }\n}\n","import { definePlugin, PluginError, z } from '@vostride/agent-qa-plugin-kit'\nimport { checkAccount } from './check.js'\nimport { deviceAnswer, DeviceHandleSchema, deviceStarted, markAppSession } from './devices.js'\nimport {\n browserStackLocalFactory,\n createTunnelManager,\n type TunnelFactory,\n type TunnelManager,\n} from './local.js'\nimport { checkEndpoints, checkLocal, options, settings } from './options.js'\nimport { uploadApp } from './upload.js'\nimport { markWebSession, playwrightAnswer, WebHandleSchema, webStarted } from './web.js'\n\nconst HandleSchema = z.union([WebHandleSchema, DeviceHandleSchema])\n\nexport interface BrowserStackPluginInput {\n tunnelFactory?: TunnelFactory\n}\n\nexport function createBrowserStackPlugin(input: BrowserStackPluginInput = {}) {\n const tunnelFactory = input.tunnelFactory ?? browserStackLocalFactory\n let playwrightVersion = 'unknown'\n let tunnel: TunnelManager | undefined\n const appSessions = new Map<string, string>()\n const webSessions = new Map<string, string>()\n\n return definePlugin({\n options,\n settings,\n serves: ['playwright', 'appium'],\n runFacts: ['testName', 'runId', 'failure'],\n skill: './skill',\n\n initialize({ options, host }) {\n checkEndpoints(options)\n checkLocal(options)\n playwrightVersion = host.playwrightVersion\n tunnel = createTunnelManager({\n factory: tunnelFactory,\n accessKey: options.accessKey,\n local: options.local,\n localIdentifier: options.localIdentifier,\n localBinary: options.localBinary,\n })\n },\n\n check({ options }, ctx) {\n return checkAccount({ options, playwrightVersion, signal: ctx.signal })\n },\n\n async acquire({ options, settings, session, run }, ctx) {\n const localIdentifier = tunnel?.identifier\n const answer =\n session.platform === 'web'\n ? playwrightAnswer({\n options,\n settings,\n engine: session.engine,\n playwrightVersion,\n testName: run?.testName,\n runId: ctx.runFacts?.runId,\n localIdentifier,\n })\n : deviceAnswer({\n options,\n settings,\n platform: session.platform,\n capabilities: session.capabilities,\n testName: run?.testName,\n runId: ctx.runFacts?.runId,\n localIdentifier,\n })\n await tunnel?.ensure(ctx.signal, ctx.progress)\n return answer\n },\n\n uploadApp({ options, path, sha256, size }, ctx) {\n return uploadApp({ options, path, sha256, size, signal: ctx.signal, progress: ctx.progress })\n },\n\n async started({ options, handle, sessionId }, ctx) {\n const web = WebHandleSchema.safeParse(handle)\n if (web.success) {\n const { session, link } = await webStarted({\n options,\n handle: web.data,\n signal: ctx.signal,\n })\n if (session !== undefined) webSessions.set(web.data.id, session.hashedId)\n return link === undefined ? {} : { link }\n }\n const device = DeviceHandleSchema.safeParse(handle)\n if (!device.success) return {}\n if (sessionId !== undefined) appSessions.set(device.data.id, sessionId)\n return deviceStarted({ options, handle: device.data, sessionId, signal: ctx.signal })\n },\n\n async end({ options, handle, outcome, reason }, ctx) {\n const web = WebHandleSchema.safeParse(handle)\n if (web.success) {\n const sessionId = webSessions.get(web.data.id)\n if (sessionId === undefined) return\n await markWebSession({ options, sessionId, outcome, reason, signal: ctx.signal })\n return\n }\n const device = DeviceHandleSchema.safeParse(handle)\n if (!device.success) return\n const sessionId = appSessions.get(device.data.id)\n if (sessionId === undefined) return\n await markAppSession({ options, sessionId, outcome, reason, signal: ctx.signal })\n },\n\n release({ handle }) {\n const parsed = HandleSchema.safeParse(handle)\n if (!parsed.success) {\n throw new PluginError('A BrowserStack handle names its kind of session.', {\n kind: 'refused',\n })\n }\n if (parsed.data.kind === 'device') appSessions.delete(parsed.data.id)\n else webSessions.delete(parsed.data.id)\n },\n\n async shutdown() {\n await tunnel?.stop()\n },\n })\n}\n\nexport default createBrowserStackPlugin()\n"],"mappings":"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"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as createBrowserStackPlugin, n as default } from "./plugin-fBvi25yB.js";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"os": {
|
|
6
|
+
"default": "OS X",
|
|
7
|
+
"description": "The operating system, for example Windows",
|
|
8
|
+
"type": "string",
|
|
9
|
+
"minLength": 1
|
|
10
|
+
},
|
|
11
|
+
"osVersion": {
|
|
12
|
+
"default": "Sequoia",
|
|
13
|
+
"description": "The operating system version, for example 11 or Sequoia",
|
|
14
|
+
"type": "string",
|
|
15
|
+
"minLength": 1
|
|
16
|
+
},
|
|
17
|
+
"browser": {
|
|
18
|
+
"description": "BrowserStack's browser; by default it follows the test's engine",
|
|
19
|
+
"type": "string",
|
|
20
|
+
"enum": [
|
|
21
|
+
"chrome",
|
|
22
|
+
"edge",
|
|
23
|
+
"playwright-chromium",
|
|
24
|
+
"playwright-firefox",
|
|
25
|
+
"playwright-webkit"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"browserVersion": {
|
|
29
|
+
"description": "The Chrome or Edge version, for example latest or latest-1",
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1
|
|
32
|
+
},
|
|
33
|
+
"project": {
|
|
34
|
+
"default": "agent-qa",
|
|
35
|
+
"description": "The BrowserStack project the sessions are filed under",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"minLength": 1
|
|
38
|
+
},
|
|
39
|
+
"build": {
|
|
40
|
+
"description": "The BrowserStack build name; by default the run id",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"minLength": 1
|
|
43
|
+
},
|
|
44
|
+
"appiumVersion": {
|
|
45
|
+
"description": "The Appium version device sessions ask for; by default the plugin's own",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"minLength": 1
|
|
48
|
+
},
|
|
49
|
+
"extra": {
|
|
50
|
+
"description": "Capabilities merged last, overriding the ones above",
|
|
51
|
+
"type": "object",
|
|
52
|
+
"propertyNames": {
|
|
53
|
+
"type": "string"
|
|
54
|
+
},
|
|
55
|
+
"additionalProperties": {}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"additionalProperties": false
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,81 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vostride/agent-qa-plugin-browserstack",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "agent-qa plugin for BrowserStack: runs web tests on BrowserStack browsers over the Playwright protocol and device tests on BrowserStack devices over Appium.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"agent-qa",
|
|
8
|
+
"agent-qa-plugin",
|
|
9
|
+
"browserstack"
|
|
10
|
+
],
|
|
11
|
+
"license": "FSL-1.1-ALv2",
|
|
12
|
+
"homepage": "https://github.com/vostride/agent-qa#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/vostride/agent-qa/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/vostride/agent-qa.git",
|
|
19
|
+
"directory": "plugins/browserstack"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=24"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"registry": "https://registry.npmjs.org/"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"exports": {
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"skill",
|
|
35
|
+
"LICENSE.md",
|
|
36
|
+
"NOTICE.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsdown && node scripts/emit.mjs",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"clean": "rm -rf dist .turbo"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@vostride/agent-qa-plugin-kit": "0.3.0",
|
|
46
|
+
"browserstack-local": "1.5.16"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "~24.19.2",
|
|
50
|
+
"@vostride/agent-qa-arch": "0.3.0",
|
|
51
|
+
"tsdown": "~0.23.4",
|
|
52
|
+
"typescript": "~7.0.2",
|
|
53
|
+
"vitest": "~5.0.5"
|
|
54
|
+
},
|
|
55
|
+
"agent-qa": {
|
|
56
|
+
"plugin": {
|
|
57
|
+
"kind": "remote",
|
|
58
|
+
"runs": "process",
|
|
59
|
+
"protocol": 1,
|
|
60
|
+
"entry": "./dist/main.js",
|
|
61
|
+
"serves": [
|
|
62
|
+
"playwright",
|
|
63
|
+
"appium"
|
|
64
|
+
],
|
|
65
|
+
"methods": [
|
|
66
|
+
"check",
|
|
67
|
+
"uploadApp",
|
|
68
|
+
"started",
|
|
69
|
+
"end"
|
|
70
|
+
],
|
|
71
|
+
"options": "./dist/options.schema.json",
|
|
72
|
+
"settings": "./dist/settings.schema.json",
|
|
73
|
+
"runFacts": [
|
|
74
|
+
"testName",
|
|
75
|
+
"runId",
|
|
76
|
+
"failure"
|
|
77
|
+
],
|
|
78
|
+
"skill": "./skill"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-qa-plugin-browserstack
|
|
3
|
+
description: Use when configuring agent-qa web or device tests to run on BrowserStack through the BrowserStack plugin, or when a BrowserStack remote is refused.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BrowserStack remotes for agent-qa
|
|
7
|
+
|
|
8
|
+
The BrowserStack plugin runs an agent-qa web test on a BrowserStack browser over the Playwright
|
|
9
|
+
protocol, and a device test on a BrowserStack Android or iOS device over Appium. It returns each
|
|
10
|
+
session's BrowserStack dashboard link and marks the session passed or failed when the test ends.
|
|
11
|
+
|
|
12
|
+
## Declare the plugin
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
plugins:
|
|
16
|
+
- name: browserstack
|
|
17
|
+
package: "@vostride/agent-qa-plugin-browserstack"
|
|
18
|
+
options:
|
|
19
|
+
username: "{{secret:BROWSERSTACK_USERNAME}}"
|
|
20
|
+
accessKey: "{{secret:BROWSERSTACK_ACCESS_KEY}}"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `username` and `accessKey` are credentials. Write them as `{{secret:<name>}}` references to
|
|
24
|
+
names in the file `workspace.secretsFile` names. Never write the values in the config.
|
|
25
|
+
- `apiUrl`, `appApiUrl`, `hubUrl` and `playwrightUrl` default to BrowserStack's own endpoints.
|
|
26
|
+
Leave them unset unless BrowserStack tells you otherwise.
|
|
27
|
+
- Install the package at the exact version of agent-qa the project runs:
|
|
28
|
+
`npm install --save-dev --save-exact @vostride/agent-qa-plugin-browserstack@<agent-qa version>`.
|
|
29
|
+
`agent-qa plugins list` prints that line when the package is missing or the version differs.
|
|
30
|
+
|
|
31
|
+
## A web remote
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
registry:
|
|
35
|
+
remotes:
|
|
36
|
+
- name: browserstack
|
|
37
|
+
plugin: browserstack
|
|
38
|
+
settings:
|
|
39
|
+
os: Windows
|
|
40
|
+
osVersion: "11"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `use.browser.remote: browserstack` in the config, a suite or a test runs the test there.
|
|
44
|
+
- Settings: `os` (default `OS X`), `osVersion` (default `Sequoia`), `browser`, `browserVersion`,
|
|
45
|
+
`project` (default `agent-qa`), `build` (default the run id) and `extra`, merged last into the
|
|
46
|
+
capabilities.
|
|
47
|
+
- `browser` follows the test's engine by default: `chrome` for Chromium, `playwright-firefox` for
|
|
48
|
+
Firefox, `playwright-webkit` for WebKit. A browser that cannot run the test's engine is refused
|
|
49
|
+
at `settings.browser`.
|
|
50
|
+
- Web tests use the Playwright protocol, so BrowserStack must list agent-qa's Playwright version.
|
|
51
|
+
The plugin carries BrowserStack's list. When agent-qa's major.minor is not on it, every web test
|
|
52
|
+
on BrowserStack is refused with kind `version` and one line naming both versions. Run the test
|
|
53
|
+
here or on another remote until a plugin release adds the version.
|
|
54
|
+
|
|
55
|
+
## A device remote
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
registry:
|
|
59
|
+
remotes:
|
|
60
|
+
- name: browserstack-devices
|
|
61
|
+
plugin: browserstack
|
|
62
|
+
settings:
|
|
63
|
+
appiumVersion: "3.5.2"
|
|
64
|
+
devices:
|
|
65
|
+
bs-pixel:
|
|
66
|
+
platform: android
|
|
67
|
+
remote: browserstack-devices
|
|
68
|
+
model: Google Pixel 9
|
|
69
|
+
os: "15"
|
|
70
|
+
targets:
|
|
71
|
+
shop-android:
|
|
72
|
+
platform: android
|
|
73
|
+
appPackage: com.example.shop
|
|
74
|
+
device: bs-pixel
|
|
75
|
+
app:
|
|
76
|
+
ref: bs://f7c874f21852ba57957a3fdc33f47514288c4ba4
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- The session asks BrowserStack's hub with `bstack:options`: the credentials, `appiumVersion`,
|
|
80
|
+
`projectName`, `buildName` (the run id) and `sessionName` (the test's name).
|
|
81
|
+
- `appiumVersion` is the device's own `bstack:options.appiumVersion`, then
|
|
82
|
+
`settings.appiumVersion`, then the plugin's default, `3.5.2`.
|
|
83
|
+
- `project`, `build` and `extra` work as on a web remote; `extra` merges into `bstack:options`.
|
|
84
|
+
|
|
85
|
+
## Apps
|
|
86
|
+
|
|
87
|
+
- `app.ref` wins. When a target sets it (a `bs://` id from an earlier upload, or an `https` URL),
|
|
88
|
+
BrowserStack installs that app and nothing is uploaded.
|
|
89
|
+
- The plugin's `uploadApp` takes the target's `app.path`. It uploads the file once per content,
|
|
90
|
+
under the custom id `agent-qa-<first 32 hex of the file's SHA-256>`, and reuses the newest
|
|
91
|
+
upload listed under that id after that. It answers the `bs://` ref the session installs.
|
|
92
|
+
BrowserStack keeps an upload for 30 days.
|
|
93
|
+
|
|
94
|
+
## BrowserStack Local
|
|
95
|
+
|
|
96
|
+
Use BrowserStack Local when the target is reachable only from this machine, for example
|
|
97
|
+
`localhost` or a host behind a VPN.
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
plugins:
|
|
101
|
+
- name: browserstack
|
|
102
|
+
package: "@vostride/agent-qa-plugin-browserstack"
|
|
103
|
+
options:
|
|
104
|
+
username: "{{secret:BROWSERSTACK_USERNAME}}"
|
|
105
|
+
accessKey: "{{secret:BROWSERSTACK_ACCESS_KEY}}"
|
|
106
|
+
local: true
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- `local: true` starts the plugin's own tunnel on the run's first acquire, with a fresh identifier
|
|
110
|
+
(or `localIdentifier` when set too), and every web and device session of the run asks for it.
|
|
111
|
+
The plugin stops the tunnel when the run ends, on stdin close and on `SIGTERM`.
|
|
112
|
+
- `localIdentifier` alone uses a tunnel you already run under that identifier; nothing is started.
|
|
113
|
+
- The tunnel comes from BrowserStack's `browserstack-local` package, which finds or downloads
|
|
114
|
+
BrowserStack's binary. `localBinary` names a binary to run instead and needs `local: true`.
|
|
115
|
+
- The tunnel logs to a fresh file under the temp directory, never into the project.
|
|
116
|
+
- A tunnel that cannot start refuses the session with kind `unreachable` at path `options.local`,
|
|
117
|
+
or `options.localBinary` when that is set; the line names the tunnel's log.
|
|
118
|
+
- Before it starts the tunnel, the plugin checks that this machine can run the binary. A binary
|
|
119
|
+
this machine cannot run refuses the session the same way, with no log because the tunnel never
|
|
120
|
+
ran, and the line's fix follows the cause:
|
|
121
|
+
- `EBADARCH`, a Mac binary built for another CPU: install Rosetta
|
|
122
|
+
(`softwareupdate --install-rosetta --agree-to-license`) or set `localBinary` to a binary
|
|
123
|
+
built for this Mac. `browserstack-local` downloads BrowserStack's Intel build on every Mac,
|
|
124
|
+
which an Apple Silicon Mac runs only under Rosetta.
|
|
125
|
+
- `ENOENT`, a path that does not exist: set `localBinary` to the path of a BrowserStackLocal
|
|
126
|
+
binary.
|
|
127
|
+
- `EACCES`, a file that is not executable: make it executable, or set `localBinary` to one that
|
|
128
|
+
is.
|
|
129
|
+
- Any other failure: set `localBinary` to a binary this machine can run.
|
|
130
|
+
- BrowserStack's tunnel binary takes the access key as `--key` on its command line, by
|
|
131
|
+
BrowserStack's design, so other users of the same machine can read it with `ps`.
|
|
132
|
+
|
|
133
|
+
## Links and marks
|
|
134
|
+
|
|
135
|
+
- When a session starts, the plugin returns its dashboard link to agent-qa: BrowserStack's
|
|
136
|
+
`browser_url`, which needs a BrowserStack login. The shareable `public_url` is never returned.
|
|
137
|
+
- A passed test is marked `passed`. A failed test, or agent-qa's own error, is marked `failed`
|
|
138
|
+
with the reason. A cancelled test stays unmarked.
|
|
139
|
+
- A web session is found through BrowserStack's REST API: the one running session in the build
|
|
140
|
+
named for the run, whose name is the test's and that started after agent-qa asked for it. When
|
|
141
|
+
none or several match, the test gets no link and the session stays unmarked, with one line on
|
|
142
|
+
the plugin's stderr. A guessed session is never marked.
|
|
143
|
+
|
|
144
|
+
## When a remote is refused
|
|
145
|
+
|
|
146
|
+
- `auth`: BrowserStack refused the username or access key. Check the secrets' values.
|
|
147
|
+
- `capacity`: BrowserStack answered 429. The account's parallel sessions are in use, or the API
|
|
148
|
+
rate limit was hit. Wait, or lower the run's concurrency.
|
|
149
|
+
- `version`: BrowserStack does not list agent-qa's Playwright version. Web tests cannot run there.
|
|
150
|
+
- `refused` with a path such as `settings.browser` or `apiUrl`: that field is wrong. The line says
|
|
151
|
+
why.
|
|
152
|
+
- `refused` with no path: BrowserStack answered another error. The line has its status and
|
|
153
|
+
message.
|
|
154
|
+
- `unreachable`: BrowserStack could not be reached. Check the network. At path `options.local` or
|
|
155
|
+
`options.localBinary`, the BrowserStack Local tunnel did not start. When the tunnel ran, its log
|
|
156
|
+
is named in the line; when this machine cannot run the binary, the line says how to fix it.
|
|
157
|
+
|
|
158
|
+
## Check and describe
|
|
159
|
+
|
|
160
|
+
- `agent-qa doctor` runs the plugin's account check. It reads the Automate and App Automate plans
|
|
161
|
+
and reports running, allowed and queued parallel sessions for each. It warns when the
|
|
162
|
+
credentials are refused, when a product is not on the account and when BrowserStack does not
|
|
163
|
+
list agent-qa's Playwright version. It opens no session.
|
|
164
|
+
- `agent-qa plugins describe <entry> --json` lists every option and setting with its type,
|
|
165
|
+
default and description.
|