 aacb45156b
			
		
	
	aacb45156b
	
	
	
		
			
			- Install Jest for unit testing with React Testing Library - Install Playwright for end-to-end testing - Configure Jest with proper TypeScript support and module mapping - Create test setup files and utilities for both unit and e2e tests Components: * Jest configuration with coverage thresholds * Playwright configuration with browser automation * Unit tests for LoginForm, AuthContext, and useSocketIO hook * E2E tests for authentication, dashboard, and agents workflows * GitHub Actions workflow for automated testing * Mock data and API utilities for consistent testing * Test documentation with best practices Testing features: - Unit tests with 70% coverage threshold - E2E tests with API mocking and user journey testing - CI/CD integration for automated test runs - Cross-browser testing support with Playwright - Authentication system testing end-to-end 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			671 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			671 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "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_util = require("./util");
 | |
| 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." });
 | |
|       }, { internal: true });
 | |
|       return;
 | |
|     }
 | |
|     const browser = await playwright[browserName].launch();
 | |
|     await use(browser);
 | |
|     await browser._wrapApiCall(async () => {
 | |
|       await browser.close({ reason: "Test ended." });
 | |
|     }, { internal: 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 }, use, testInfo) => {
 | |
|     testInfo.setTimeout(testInfo.project.timeout);
 | |
|     const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
 | |
|     await artifactsRecorder.willStartTest(testInfo);
 | |
|     const tracingGroupSteps = [];
 | |
|     const csiListener = {
 | |
|       onApiCallBegin: (data, channel) => {
 | |
|         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") {
 | |
|           if (zone.apiName)
 | |
|             data.apiName = zone.apiName;
 | |
|           if (zone.title)
 | |
|             data.title = (0, import_util.stepTitle)(zone.category, zone.title);
 | |
|           data.stepId = zone.stepId;
 | |
|           return;
 | |
|         }
 | |
|         const step = testInfo2._addStep({
 | |
|           location: data.frames[0],
 | |
|           category: "pw:api",
 | |
|           title: renderTitle(channel.type, channel.method, channel.params, data.title),
 | |
|           apiName: data.apiName,
 | |
|           params: channel.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);
 | |
|         }, { internal: true });
 | |
|       } else if (process.env.PW_CLOCK === "realtime") {
 | |
|         await context._wrapApiCall(async () => {
 | |
|           await context.clock.install({ time: 0 });
 | |
|         }, { internal: 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 });
 | |
|       }, { internal: 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 }],
 | |
|   _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);
 | |
|     await use(context);
 | |
|     const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
 | |
|     await browser._disconnectFromReusedContext(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");
 | |
| 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) {
 | |
|     this._playwright = playwright;
 | |
|     this._artifactsDir = artifactsDir;
 | |
|     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((context) => 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) {
 | |
|     await this._stopTracing(context.tracing);
 | |
|     await this._screenshotRecorder.captureTemporary(context);
 | |
|     await this._takePageSnapshot(context);
 | |
|   }
 | |
|   async _takePageSnapshot(context) {
 | |
|     if (process.env.PLAYWRIGHT_NO_COPY_PROMPT)
 | |
|       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();
 | |
|     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._pageSnapshot && this._testInfo.errors.length > 0 && !this._testInfo.attachments.some((a) => a.name === "error-context")) {
 | |
|       const lines = [
 | |
|         "# Page snapshot",
 | |
|         "",
 | |
|         "```yaml",
 | |
|         this._pageSnapshot,
 | |
|         "```"
 | |
|       ];
 | |
|       const filePath = this._testInfo.outputPath("error-context.md");
 | |
|       await import_fs.default.promises.writeFile(filePath, lines.join("\n"), "utf8");
 | |
|       this._testInfo._attach({
 | |
|         name: "error-context",
 | |
|         contentType: "text/markdown",
 | |
|         path: filePath
 | |
|       }, void 0);
 | |
|     }
 | |
|   }
 | |
|   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 renderTitle(type, method, params, title) {
 | |
|   const prefix = (0, import_utils.renderTitleForCall)({ title, type, method, params });
 | |
|   let selector;
 | |
|   if (params?.["selector"] && typeof params.selector === "string")
 | |
|     selector = (0, import_utils.asLocatorDescription)("javascript", params.selector);
 | |
|   return prefix + (selector ? ` ${selector}` : "");
 | |
| }
 | |
| 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
 | |
| });
 |