Skip to main content
Glama

Firefox MCP Server

by JediLuke
index.js29.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var index_exports = {}; __export(index_exports, { _baseTest: () => _baseTest, defineConfig: () => import_configLoader.defineConfig, expect: () => import_expect.expect, mergeExpects: () => import_expect2.mergeExpects, mergeTests: () => import_testType2.mergeTests, test: () => test }); module.exports = __toCommonJS(index_exports); var import_fs = __toESM(require("fs")); var import_path = __toESM(require("path")); var playwrightLibrary = __toESM(require("playwright-core")); var import_utils = require("playwright-core/lib/utils"); var import_globals = require("./common/globals"); var import_testType = require("./common/testType"); var import_errorContext = require("./errorContext"); var import_expect = require("./matchers/expect"); var import_configLoader = require("./common/configLoader"); var import_testType2 = require("./common/testType"); var import_expect2 = require("./matchers/expect"); const _baseTest = import_testType.rootTestType.test; (0, import_utils.setBoxedStackPrefixes)([import_path.default.dirname(require.resolve("../package.json"))]); if (process["__pw_initiator__"]) { const originalStackTraceLimit = Error.stackTraceLimit; Error.stackTraceLimit = 200; try { throw new Error("Requiring @playwright/test second time, \nFirst:\n" + process["__pw_initiator__"] + "\n\nSecond: "); } finally { Error.stackTraceLimit = originalStackTraceLimit; } } else { process["__pw_initiator__"] = new Error().stack; } const playwrightFixtures = { defaultBrowserType: ["chromium", { scope: "worker", option: true }], browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true }], playwright: [async ({}, use) => { await use(require("playwright-core")); }, { scope: "worker", box: true }], headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true }], channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true }], launchOptions: [{}, { scope: "worker", option: true }], connectOptions: [async ({ _optionConnectOptions }, use) => { await use(connectOptionsFromEnv() || _optionConnectOptions); }, { scope: "worker", option: true }], screenshot: ["off", { scope: "worker", option: true }], video: ["off", { scope: "worker", option: true }], trace: ["off", { scope: "worker", option: true }], _browserOptions: [async ({ playwright, headless, channel, launchOptions }, use) => { const options = { handleSIGINT: false, ...launchOptions, tracesDir: tracing().tracesDir() }; if (headless !== void 0) options.headless = headless; if (channel !== void 0) options.channel = channel; playwright._defaultLaunchOptions = options; await use(options); playwright._defaultLaunchOptions = void 0; }, { scope: "worker", auto: true, box: true }], browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use, testInfo) => { if (!["chromium", "firefox", "webkit", "_bidiChromium", "_bidiFirefox"].includes(browserName)) throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`); if (connectOptions) { const browser2 = await playwright[browserName].connect({ ...connectOptions, exposeNetwork: connectOptions.exposeNetwork ?? connectOptions._exposeNetwork, headers: { // HTTP headers are ASCII only (not UTF-8). "x-playwright-launch-options": (0, import_utils.jsonStringifyForceASCII)(_browserOptions), ...connectOptions.headers } }); await use(browser2); await browser2._wrapApiCall(async () => { await browser2.close({ reason: "Test ended." }); }, true); return; } const browser = await playwright[browserName].launch(); await use(browser); await browser._wrapApiCall(async () => { await browser.close({ reason: "Test ended." }); }, true); }, { scope: "worker", timeout: 0 }], acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }], bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true }], colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true }], deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true }], extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true }], geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true }], hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true }], httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true }], ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true }], isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true }], javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true }], locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true }], offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true }], permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true }], proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true }], storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true }], clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true }], timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true }], userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true }], viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true }], actionTimeout: [0, { option: true }], testIdAttribute: ["data-testid", { option: true }], navigationTimeout: [0, { option: true }], baseURL: [async ({}, use) => { await use(process.env.PLAYWRIGHT_TEST_BASE_URL); }, { option: true }], serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true }], contextOptions: [{}, { option: true }], _combinedContextOptions: [async ({ acceptDownloads, bypassCSP, clientCertificates, colorScheme, deviceScaleFactor, extraHTTPHeaders, hasTouch, geolocation, httpCredentials, ignoreHTTPSErrors, isMobile, javaScriptEnabled, locale, offline, permissions, proxy, storageState, viewport, timezoneId, userAgent, baseURL, contextOptions, serviceWorkers }, use) => { const options = {}; if (acceptDownloads !== void 0) options.acceptDownloads = acceptDownloads; if (bypassCSP !== void 0) options.bypassCSP = bypassCSP; if (colorScheme !== void 0) options.colorScheme = colorScheme; if (deviceScaleFactor !== void 0) options.deviceScaleFactor = deviceScaleFactor; if (extraHTTPHeaders !== void 0) options.extraHTTPHeaders = extraHTTPHeaders; if (geolocation !== void 0) options.geolocation = geolocation; if (hasTouch !== void 0) options.hasTouch = hasTouch; if (httpCredentials !== void 0) options.httpCredentials = httpCredentials; if (ignoreHTTPSErrors !== void 0) options.ignoreHTTPSErrors = ignoreHTTPSErrors; if (isMobile !== void 0) options.isMobile = isMobile; if (javaScriptEnabled !== void 0) options.javaScriptEnabled = javaScriptEnabled; if (locale !== void 0) options.locale = locale; if (offline !== void 0) options.offline = offline; if (permissions !== void 0) options.permissions = permissions; if (proxy !== void 0) options.proxy = proxy; if (storageState !== void 0) options.storageState = storageState; if (clientCertificates?.length) options.clientCertificates = resolveClientCerticates(clientCertificates); if (timezoneId !== void 0) options.timezoneId = timezoneId; if (userAgent !== void 0) options.userAgent = userAgent; if (viewport !== void 0) options.viewport = viewport; if (baseURL !== void 0) options.baseURL = baseURL; if (serviceWorkers !== void 0) options.serviceWorkers = serviceWorkers; await use({ ...contextOptions, ...options }); }, { box: true }], _setupContextOptions: [async ({ playwright, _combinedContextOptions, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => { if (testIdAttribute) playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute); testInfo.snapshotSuffix = process.platform; if ((0, import_utils.debugMode)()) testInfo._setDebugMode(); playwright._defaultContextOptions = _combinedContextOptions; playwright._defaultContextTimeout = actionTimeout || 0; playwright._defaultContextNavigationTimeout = navigationTimeout || 0; await use(); playwright._defaultContextOptions = void 0; playwright._defaultContextTimeout = void 0; playwright._defaultContextNavigationTimeout = void 0; }, { auto: "all-hooks-included", title: "context configuration", box: true }], _setupArtifacts: [async ({ playwright, screenshot, _optionErrorContext }, use, testInfo) => { testInfo.setTimeout(testInfo.project.timeout); const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, _optionErrorContext); await artifactsRecorder.willStartTest(testInfo); const tracingGroupSteps = []; const csiListener = { onApiCallBegin: (data) => { const testInfo2 = (0, import_globals.currentTestInfo)(); if (!testInfo2 || data.apiName.includes("setTestIdAttribute") || data.apiName === "tracing.groupEnd") return; const zone = (0, import_utils.currentZone)().data("stepZone"); if (zone && zone.category === "expect") { data.apiName = zone.title; data.stepId = zone.stepId; return; } const step = testInfo2._addStep({ location: data.frames[0], category: "pw:api", title: renderApiCall(data.apiName, data.params), apiName: data.apiName, params: data.params }, tracingGroupSteps[tracingGroupSteps.length - 1]); data.userData = step; data.stepId = step.stepId; if (data.apiName === "tracing.group") tracingGroupSteps.push(step); }, onApiCallEnd: (data) => { if (data.apiName === "tracing.group") return; if (data.apiName === "tracing.groupEnd") { const step2 = tracingGroupSteps.pop(); step2?.complete({ error: data.error }); return; } const step = data.userData; step?.complete({ error: data.error }); }, onWillPause: ({ keepTestTimeout }) => { if (!keepTestTimeout) (0, import_globals.currentTestInfo)()?._setDebugMode(); }, runAfterCreateBrowserContext: async (context) => { await artifactsRecorder?.didCreateBrowserContext(context); const testInfo2 = (0, import_globals.currentTestInfo)(); if (testInfo2) attachConnectedHeaderIfNeeded(testInfo2, context.browser()); }, runAfterCreateRequestContext: async (context) => { await artifactsRecorder?.didCreateRequestContext(context); }, runBeforeCloseBrowserContext: async (context) => { await artifactsRecorder?.willCloseBrowserContext(context); }, runBeforeCloseRequestContext: async (context) => { await artifactsRecorder?.willCloseRequestContext(context); } }; const clientInstrumentation = playwright._instrumentation; clientInstrumentation.addListener(csiListener); await use(); clientInstrumentation.removeListener(csiListener); await artifactsRecorder.didFinishTest(); }, { auto: "all-hooks-included", title: "trace recording", box: true, timeout: 0 }], _contextFactory: [async ({ browser, video, _reuseContext, _combinedContextOptions /** mitigate dep-via-auto lack of traceability */ }, use, testInfo) => { const testInfoImpl = testInfo; const videoMode = normalizeVideoMode(video); const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext; const contexts = /* @__PURE__ */ new Map(); await use(async (options) => { const hook = testInfoImpl._currentHookType(); if (hook === "beforeAll" || hook === "afterAll") { throw new Error([ `"context" and "page" fixtures are not supported in "${hook}" since they are created on a per-test basis.`, `If you would like to reuse a single page between tests, create context manually with browser.newContext(). See https://aka.ms/playwright/reuse-page for details.`, `If you would like to configure your page before each test, do that in beforeEach hook instead.` ].join("\n")); } const videoOptions = captureVideo ? { recordVideo: { dir: tracing().artifactsDir(), size: typeof video === "string" ? void 0 : video.size } } : {}; const context = await browser.newContext({ ...videoOptions, ...options }); const contextData = { pagesWithVideo: [] }; contexts.set(context, contextData); if (captureVideo) context.on("page", (page) => contextData.pagesWithVideo.push(page)); if (process.env.PW_CLOCK === "frozen") { await context._wrapApiCall(async () => { await context.clock.install({ time: 0 }); await context.clock.pauseAt(1e3); }, true); } else if (process.env.PW_CLOCK === "realtime") { await context._wrapApiCall(async () => { await context.clock.install({ time: 0 }); }, true); } return context; }); let counter = 0; const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended."; await Promise.all([...contexts.keys()].map(async (context) => { await context._wrapApiCall(async () => { await context.close({ reason: closeReason }); }, true); const testFailed = testInfo.status !== testInfo.expectedStatus; const preserveVideo = captureVideo && (videoMode === "on" || testFailed && videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1); if (preserveVideo) { const { pagesWithVideo: pagesForVideo } = contexts.get(context); const videos = pagesForVideo.map((p) => p.video()).filter(Boolean); await Promise.all(videos.map(async (v) => { try { const savedPath = testInfo.outputPath(`video${counter ? "-" + counter : ""}.webm`); ++counter; await v.saveAs(savedPath); testInfo.attachments.push({ name: "video", path: savedPath, contentType: "video/webm" }); } catch (e) { } })); } })); }, { scope: "test", title: "context", box: true }], _optionContextReuseMode: ["none", { scope: "worker", option: true }], _optionConnectOptions: [void 0, { scope: "worker", option: true }], _optionErrorContext: [process.env.PLAYWRIGHT_NO_COPY_PROMPT ? void 0 : { format: "markdown" }, { scope: "worker", option: true }], _reuseContext: [async ({ video, _optionContextReuseMode }, use) => { let mode = _optionContextReuseMode; if (process.env.PW_TEST_REUSE_CONTEXT) mode = "when-possible"; const reuse = mode === "when-possible" && normalizeVideoMode(video) === "off"; await use(reuse); }, { scope: "worker", title: "context", box: true }], context: async ({ playwright, browser, _reuseContext, _contextFactory }, use, testInfo) => { attachConnectedHeaderIfNeeded(testInfo, browser); if (!_reuseContext) { await use(await _contextFactory()); return; } const defaultContextOptions = playwright.chromium._defaultContextOptions; const context = await browser._newContextForReuse(defaultContextOptions); context[kIsReusedContext] = true; await use(context); const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended."; await browser._stopPendingOperations(closeReason); }, page: async ({ context, _reuseContext }, use) => { if (!_reuseContext) { await use(await context.newPage()); return; } let [page] = context.pages(); if (!page) page = await context.newPage(); await use(page); }, request: async ({ playwright }, use) => { const request = await playwright.request.newContext(); await use(request); const hook = test.info()._currentHookType(); if (hook === "beforeAll") { await request.dispose({ reason: [ `Fixture { request } from beforeAll cannot be reused in a test.`, ` - Recommended fix: use a separate { request } in the test.`, ` - Alternatively, manually create APIRequestContext in beforeAll and dispose it in afterAll.`, `See https://playwright.dev/docs/api-testing#sending-api-requests-from-ui-tests for more details.` ].join("\n") }); } else { await request.dispose(); } } }; function normalizeVideoMode(video) { if (!video) return "off"; let videoMode = typeof video === "string" ? video : video.mode; if (videoMode === "retry-with-video") videoMode = "on-first-retry"; return videoMode; } function shouldCaptureVideo(videoMode, testInfo) { return videoMode === "on" || videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1; } function normalizeScreenshotMode(screenshot) { if (!screenshot) return "off"; return typeof screenshot === "string" ? screenshot : screenshot.mode; } function attachConnectedHeaderIfNeeded(testInfo, browser) { const connectHeaders = browser?._connection.headers; if (!connectHeaders) return; for (const header of connectHeaders) { if (header.name !== "x-playwright-attachment") continue; const [name, value] = header.value.split("="); if (!name || !value) continue; if (testInfo.attachments.some((attachment) => attachment.name === name)) continue; testInfo.attachments.push({ name, contentType: "text/plain", body: Buffer.from(value) }); } } function resolveFileToConfig(file) { const config = test.info().config.configFile; if (!config || !file) return file; if (import_path.default.isAbsolute(file)) return file; return import_path.default.resolve(import_path.default.dirname(config), file); } function resolveClientCerticates(clientCertificates) { for (const cert of clientCertificates) { cert.certPath = resolveFileToConfig(cert.certPath); cert.keyPath = resolveFileToConfig(cert.keyPath); cert.pfxPath = resolveFileToConfig(cert.pfxPath); } return clientCertificates; } const kTracingStarted = Symbol("kTracingStarted"); const kIsReusedContext = Symbol("kReusedContext"); function connectOptionsFromEnv() { const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT; if (!wsEndpoint) return void 0; const headers = process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : void 0; return { wsEndpoint, headers, exposeNetwork: process.env.PW_TEST_CONNECT_EXPOSE_NETWORK }; } class SnapshotRecorder { constructor(_artifactsRecorder, _mode, _name, _contentType, _extension, _doSnapshot) { this._artifactsRecorder = _artifactsRecorder; this._mode = _mode; this._name = _name; this._contentType = _contentType; this._extension = _extension; this._doSnapshot = _doSnapshot; this._ordinal = 0; this._temporary = []; } fixOrdinal() { this._ordinal = this.testInfo.attachments.filter((a) => a.name === this._name).length; } shouldCaptureUponFinish() { return this._mode === "on" || this._mode === "only-on-failure" && this.testInfo._isFailure() || this._mode === "on-first-failure" && this.testInfo._isFailure() && this.testInfo.retry === 0; } async maybeCapture() { if (!this.shouldCaptureUponFinish()) return; await Promise.all(this._artifactsRecorder._playwright._allPages().map((page) => this._snapshotPage(page, false))); } async persistTemporary() { if (this.shouldCaptureUponFinish()) { await Promise.all(this._temporary.map(async (file) => { try { const path2 = this._createAttachmentPath(); await import_fs.default.promises.rename(file, path2); this._attach(path2); } catch { } })); } } async captureTemporary(context) { if (this._mode === "on" || this._mode === "only-on-failure" || this._mode === "on-first-failure" && this.testInfo.retry === 0) await Promise.all(context.pages().map((page) => this._snapshotPage(page, true))); } _attach(screenshotPath) { this.testInfo.attachments.push({ name: this._name, path: screenshotPath, contentType: this._contentType }); } _createAttachmentPath() { const testFailed = this.testInfo._isFailure(); const index = this._ordinal + 1; ++this._ordinal; const path2 = this.testInfo.outputPath(`test-${testFailed ? "failed" : "finished"}-${index}${this._extension}`); return path2; } _createTemporaryArtifact(...name) { const file = import_path.default.join(this._artifactsRecorder._artifactsDir, ...name); return file; } async _snapshotPage(page, temporary) { if (page[this.testInfo._uniqueSymbol]) return; page[this.testInfo._uniqueSymbol] = true; try { const path2 = temporary ? this._createTemporaryArtifact((0, import_utils.createGuid)() + this._extension) : this._createAttachmentPath(); await this._doSnapshot(page, path2); if (temporary) this._temporary.push(path2); else this._attach(path2); } catch { } } get testInfo() { return this._artifactsRecorder._testInfo; } } class ArtifactsRecorder { constructor(playwright, artifactsDir, screenshot, errorContext) { this._reusedContexts = /* @__PURE__ */ new Set(); this._sourceCache = /* @__PURE__ */ new Map(); this._playwright = playwright; this._artifactsDir = artifactsDir; this._errorContext = errorContext; const screenshotOptions = typeof screenshot === "string" ? void 0 : screenshot; this._startedCollectingArtifacts = Symbol("startedCollectingArtifacts"); this._screenshotRecorder = new SnapshotRecorder(this, normalizeScreenshotMode(screenshot), "screenshot", "image/png", ".png", async (page, path2) => { await page.screenshot({ ...screenshotOptions, timeout: 5e3, path: path2, caret: "initial" }); }); } async willStartTest(testInfo) { this._testInfo = testInfo; testInfo._onDidFinishTestFunction = () => this.didFinishTestFunction(); this._screenshotRecorder.fixOrdinal(); await Promise.all(this._playwright._allContexts().map(async (context) => { if (context[kIsReusedContext]) this._reusedContexts.add(context); else await this.didCreateBrowserContext(context); })); { const existingApiRequests = Array.from(this._playwright.request._contexts); await Promise.all(existingApiRequests.map((c) => this.didCreateRequestContext(c))); } } async didCreateBrowserContext(context) { await this._startTraceChunkOnContextCreation(context.tracing); } async willCloseBrowserContext(context) { if (this._reusedContexts.has(context)) return; await this._stopTracing(context.tracing); await this._screenshotRecorder.captureTemporary(context); await this._takePageSnapshot(context); } async _takePageSnapshot(context) { if (!this._errorContext) return; if (this._testInfo.errors.length === 0) return; if (this._pageSnapshot) return; const page = context.pages()[0]; try { this._pageSnapshot = await page?.locator("body").ariaSnapshot({ timeout: 5e3 }); } catch { } } async didCreateRequestContext(context) { const tracing2 = context._tracing; await this._startTraceChunkOnContextCreation(tracing2); } async willCloseRequestContext(context) { const tracing2 = context._tracing; await this._stopTracing(tracing2); } async didFinishTestFunction() { await this._screenshotRecorder.maybeCapture(); } async didFinishTest() { await this.didFinishTestFunction(); const leftoverContexts = this._playwright._allContexts().filter((context2) => !this._reusedContexts.has(context2)); const leftoverApiRequests = Array.from(this._playwright.request._contexts); await Promise.all(leftoverContexts.map(async (context2) => { await this._stopTracing(context2.tracing); }).concat(leftoverApiRequests.map(async (context2) => { const tracing2 = context2._tracing; await this._stopTracing(tracing2); }))); await this._screenshotRecorder.persistTemporary(); const context = leftoverContexts[0]; if (context) await this._takePageSnapshot(context); if (this._errorContext) await (0, import_errorContext.attachErrorContext)(this._testInfo, this._errorContext.format, this._sourceCache, this._pageSnapshot); } async _startTraceChunkOnContextCreation(tracing2) { const options = this._testInfo._tracing.traceOptions(); if (options) { const title = this._testInfo._tracing.traceTitle(); const name = this._testInfo._tracing.generateNextTraceRecordingName(); if (!tracing2[kTracingStarted]) { await tracing2.start({ ...options, title, name }); tracing2[kTracingStarted] = true; } else { await tracing2.startChunk({ title, name }); } } else { if (tracing2[kTracingStarted]) { tracing2[kTracingStarted] = false; await tracing2.stop(); } } } async _stopTracing(tracing2) { if (tracing2[this._startedCollectingArtifacts]) return; tracing2[this._startedCollectingArtifacts] = true; if (this._testInfo._tracing.traceOptions() && tracing2[kTracingStarted]) await tracing2.stopChunk({ path: this._testInfo._tracing.maybeGenerateNextTraceRecordingPath() }); } } function paramsToRender(apiName) { switch (apiName) { case "locator.fill": return ["value"]; default: return ["url", "selector", "text", "key"]; } } function renderApiCall(apiName, params) { if (apiName === "tracing.group") return params.name; const paramsArray = []; if (params) { for (const name of paramsToRender(apiName)) { if (!(name in params)) continue; let value; if (name === "selector" && (0, import_utils.isString)(params[name]) && params[name].startsWith("internal:")) { const getter = (0, import_utils.asLocator)("javascript", params[name]); apiName = apiName.replace(/^locator\./, "locator." + getter + "."); apiName = apiName.replace(/^page\./, "page." + getter + "."); apiName = apiName.replace(/^frame\./, "frame." + getter + "."); } else { value = params[name]; paramsArray.push(value); } } } const paramsText = paramsArray.length ? "(" + paramsArray.join(", ") + ")" : ""; return apiName + paramsText; } function tracing() { return test.info()._tracing; } const test = _baseTest.extend(playwrightFixtures); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { _baseTest, defineConfig, expect, mergeExpects, mergeTests, test });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JediLuke/firefox-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server