index.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. "use strict";
  2. var __create = Object.create;
  3. var __defProp = Object.defineProperty;
  4. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  5. var __getOwnPropNames = Object.getOwnPropertyNames;
  6. var __getProtoOf = Object.getPrototypeOf;
  7. var __hasOwnProp = Object.prototype.hasOwnProperty;
  8. var __export = (target, all) => {
  9. for (var name in all)
  10. __defProp(target, name, { get: all[name], enumerable: true });
  11. };
  12. var __copyProps = (to, from, except, desc) => {
  13. if (from && typeof from === "object" || typeof from === "function") {
  14. for (let key of __getOwnPropNames(from))
  15. if (!__hasOwnProp.call(to, key) && key !== except)
  16. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  17. }
  18. return to;
  19. };
  20. var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  21. // If the importer is in node compatibility mode or this is not an ESM
  22. // file that has been converted to a CommonJS file using a Babel-
  23. // compatible transform (i.e. "__esModule" has not been set), then set
  24. // "default" to the CommonJS "module.exports" for node compatibility.
  25. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  26. mod
  27. ));
  28. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  29. var index_exports = {};
  30. __export(index_exports, {
  31. _baseTest: () => _baseTest,
  32. defineConfig: () => import_configLoader.defineConfig,
  33. expect: () => import_expect.expect,
  34. mergeExpects: () => import_expect2.mergeExpects,
  35. mergeTests: () => import_testType2.mergeTests,
  36. test: () => test
  37. });
  38. module.exports = __toCommonJS(index_exports);
  39. var import_fs = __toESM(require("fs"));
  40. var import_path = __toESM(require("path"));
  41. var playwrightLibrary = __toESM(require("playwright-core"));
  42. var import_utils = require("playwright-core/lib/utils");
  43. var import_globals = require("./common/globals");
  44. var import_testType = require("./common/testType");
  45. var import_expect = require("./matchers/expect");
  46. var import_configLoader = require("./common/configLoader");
  47. var import_testType2 = require("./common/testType");
  48. var import_expect2 = require("./matchers/expect");
  49. const _baseTest = import_testType.rootTestType.test;
  50. (0, import_utils.setBoxedStackPrefixes)([import_path.default.dirname(require.resolve("../package.json"))]);
  51. if (process["__pw_initiator__"]) {
  52. const originalStackTraceLimit = Error.stackTraceLimit;
  53. Error.stackTraceLimit = 200;
  54. try {
  55. throw new Error("Requiring @playwright/test second time, \nFirst:\n" + process["__pw_initiator__"] + "\n\nSecond: ");
  56. } finally {
  57. Error.stackTraceLimit = originalStackTraceLimit;
  58. }
  59. } else {
  60. process["__pw_initiator__"] = new Error().stack;
  61. }
  62. const playwrightFixtures = {
  63. defaultBrowserType: ["chromium", { scope: "worker", option: true, box: true }],
  64. browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true, box: true }],
  65. playwright: [async ({}, use) => {
  66. await use(require("playwright-core"));
  67. }, { scope: "worker", box: true }],
  68. headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true, box: true }],
  69. channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true, box: true }],
  70. launchOptions: [{}, { scope: "worker", option: true, box: true }],
  71. connectOptions: [async ({ _optionConnectOptions }, use) => {
  72. await use(connectOptionsFromEnv() || _optionConnectOptions);
  73. }, { scope: "worker", option: true, box: true }],
  74. screenshot: ["off", { scope: "worker", option: true, box: true }],
  75. video: ["off", { scope: "worker", option: true, box: true }],
  76. trace: ["off", { scope: "worker", option: true, box: true }],
  77. _browserOptions: [async ({ playwright, headless, channel, launchOptions }, use) => {
  78. const options = {
  79. handleSIGINT: false,
  80. ...launchOptions,
  81. tracesDir: tracing().tracesDir()
  82. };
  83. if (headless !== void 0)
  84. options.headless = headless;
  85. if (channel !== void 0)
  86. options.channel = channel;
  87. playwright._defaultLaunchOptions = options;
  88. await use(options);
  89. playwright._defaultLaunchOptions = void 0;
  90. }, { scope: "worker", auto: true, box: true }],
  91. browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use, testInfo) => {
  92. if (!["chromium", "firefox", "webkit", "_bidiChromium", "_bidiFirefox"].includes(browserName))
  93. throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
  94. if (connectOptions) {
  95. const browser2 = await playwright[browserName].connect({
  96. ...connectOptions,
  97. exposeNetwork: connectOptions.exposeNetwork ?? connectOptions._exposeNetwork,
  98. headers: {
  99. // HTTP headers are ASCII only (not UTF-8).
  100. "x-playwright-launch-options": (0, import_utils.jsonStringifyForceASCII)(_browserOptions),
  101. ...connectOptions.headers
  102. }
  103. });
  104. await use(browser2);
  105. await browser2.close({ reason: "Test ended." });
  106. return;
  107. }
  108. const browser = await playwright[browserName].launch();
  109. await use(browser);
  110. await browser.close({ reason: "Test ended." });
  111. }, { scope: "worker", timeout: 0 }],
  112. acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true, box: true }],
  113. bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true, box: true }],
  114. colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true, box: true }],
  115. deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true, box: true }],
  116. extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true, box: true }],
  117. geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true, box: true }],
  118. hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true, box: true }],
  119. httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true, box: true }],
  120. ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true, box: true }],
  121. isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true, box: true }],
  122. javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true, box: true }],
  123. locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true, box: true }],
  124. offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true, box: true }],
  125. permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true, box: true }],
  126. proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true, box: true }],
  127. storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true, box: true }],
  128. clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true, box: true }],
  129. timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true, box: true }],
  130. userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true, box: true }],
  131. viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true, box: true }],
  132. actionTimeout: [0, { option: true, box: true }],
  133. testIdAttribute: ["data-testid", { option: true, box: true }],
  134. navigationTimeout: [0, { option: true, box: true }],
  135. baseURL: [async ({}, use) => {
  136. await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
  137. }, { option: true, box: true }],
  138. serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true, box: true }],
  139. contextOptions: [{}, { option: true, box: true }],
  140. _combinedContextOptions: [async ({
  141. acceptDownloads,
  142. bypassCSP,
  143. clientCertificates,
  144. colorScheme,
  145. deviceScaleFactor,
  146. extraHTTPHeaders,
  147. hasTouch,
  148. geolocation,
  149. httpCredentials,
  150. ignoreHTTPSErrors,
  151. isMobile,
  152. javaScriptEnabled,
  153. locale,
  154. offline,
  155. permissions,
  156. proxy,
  157. storageState,
  158. viewport,
  159. timezoneId,
  160. userAgent,
  161. baseURL,
  162. contextOptions,
  163. serviceWorkers
  164. }, use) => {
  165. const options = {};
  166. if (acceptDownloads !== void 0)
  167. options.acceptDownloads = acceptDownloads;
  168. if (bypassCSP !== void 0)
  169. options.bypassCSP = bypassCSP;
  170. if (colorScheme !== void 0)
  171. options.colorScheme = colorScheme;
  172. if (deviceScaleFactor !== void 0)
  173. options.deviceScaleFactor = deviceScaleFactor;
  174. if (extraHTTPHeaders !== void 0)
  175. options.extraHTTPHeaders = extraHTTPHeaders;
  176. if (geolocation !== void 0)
  177. options.geolocation = geolocation;
  178. if (hasTouch !== void 0)
  179. options.hasTouch = hasTouch;
  180. if (httpCredentials !== void 0)
  181. options.httpCredentials = httpCredentials;
  182. if (ignoreHTTPSErrors !== void 0)
  183. options.ignoreHTTPSErrors = ignoreHTTPSErrors;
  184. if (isMobile !== void 0)
  185. options.isMobile = isMobile;
  186. if (javaScriptEnabled !== void 0)
  187. options.javaScriptEnabled = javaScriptEnabled;
  188. if (locale !== void 0)
  189. options.locale = locale;
  190. if (offline !== void 0)
  191. options.offline = offline;
  192. if (permissions !== void 0)
  193. options.permissions = permissions;
  194. if (proxy !== void 0)
  195. options.proxy = proxy;
  196. if (storageState !== void 0)
  197. options.storageState = storageState;
  198. if (clientCertificates?.length)
  199. options.clientCertificates = resolveClientCerticates(clientCertificates);
  200. if (timezoneId !== void 0)
  201. options.timezoneId = timezoneId;
  202. if (userAgent !== void 0)
  203. options.userAgent = userAgent;
  204. if (viewport !== void 0)
  205. options.viewport = viewport;
  206. if (baseURL !== void 0)
  207. options.baseURL = baseURL;
  208. if (serviceWorkers !== void 0)
  209. options.serviceWorkers = serviceWorkers;
  210. await use({
  211. ...contextOptions,
  212. ...options
  213. });
  214. }, { box: true }],
  215. _setupContextOptions: [async ({ playwright, _combinedContextOptions, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => {
  216. if (testIdAttribute)
  217. playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
  218. testInfo.snapshotSuffix = process.platform;
  219. if ((0, import_utils.debugMode)() === "inspector")
  220. testInfo._setDebugMode();
  221. playwright._defaultContextOptions = _combinedContextOptions;
  222. playwright._defaultContextTimeout = actionTimeout || 0;
  223. playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
  224. await use();
  225. playwright._defaultContextOptions = void 0;
  226. playwright._defaultContextTimeout = void 0;
  227. playwright._defaultContextNavigationTimeout = void 0;
  228. }, { auto: "all-hooks-included", title: "context configuration", box: true }],
  229. _setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => {
  230. testInfo.setTimeout(testInfo.project.timeout);
  231. const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
  232. await artifactsRecorder.willStartTest(testInfo);
  233. const tracingGroupSteps = [];
  234. const csiListener = {
  235. onApiCallBegin: (data, channel) => {
  236. const testInfo2 = (0, import_globals.currentTestInfo)();
  237. if (!testInfo2 || data.apiName.includes("setTestIdAttribute") || data.apiName === "tracing.groupEnd")
  238. return;
  239. const zone = (0, import_utils.currentZone)().data("stepZone");
  240. if (zone && zone.category === "expect") {
  241. if (zone.apiName)
  242. data.apiName = zone.apiName;
  243. if (zone.title)
  244. data.title = zone.title;
  245. data.stepId = zone.stepId;
  246. return;
  247. }
  248. const step = testInfo2._addStep({
  249. location: data.frames[0],
  250. category: "pw:api",
  251. title: renderTitle(channel.type, channel.method, channel.params, data.title),
  252. apiName: data.apiName,
  253. params: channel.params,
  254. group: (0, import_utils.getActionGroup)({ type: channel.type, method: channel.method })
  255. }, tracingGroupSteps[tracingGroupSteps.length - 1]);
  256. data.userData = step;
  257. data.stepId = step.stepId;
  258. if (data.apiName === "tracing.group")
  259. tracingGroupSteps.push(step);
  260. },
  261. onApiCallRecovery: (data, error, recoveryHandlers) => {
  262. const step = data.userData;
  263. if (step)
  264. recoveryHandlers.push(() => step.recoverFromStepError(error));
  265. },
  266. onApiCallEnd: (data) => {
  267. if (data.apiName === "tracing.group")
  268. return;
  269. if (data.apiName === "tracing.groupEnd") {
  270. const step2 = tracingGroupSteps.pop();
  271. step2?.complete({ error: data.error });
  272. return;
  273. }
  274. const step = data.userData;
  275. step?.complete({ error: data.error });
  276. },
  277. onWillPause: ({ keepTestTimeout }) => {
  278. if (!keepTestTimeout)
  279. (0, import_globals.currentTestInfo)()?._setDebugMode();
  280. },
  281. runAfterCreateBrowserContext: async (context) => {
  282. await artifactsRecorder?.didCreateBrowserContext(context);
  283. const testInfo2 = (0, import_globals.currentTestInfo)();
  284. if (testInfo2)
  285. attachConnectedHeaderIfNeeded(testInfo2, context.browser());
  286. },
  287. runAfterCreateRequestContext: async (context) => {
  288. await artifactsRecorder?.didCreateRequestContext(context);
  289. },
  290. runBeforeCloseBrowserContext: async (context) => {
  291. await artifactsRecorder?.willCloseBrowserContext(context);
  292. },
  293. runBeforeCloseRequestContext: async (context) => {
  294. await artifactsRecorder?.willCloseRequestContext(context);
  295. }
  296. };
  297. const clientInstrumentation = playwright._instrumentation;
  298. clientInstrumentation.addListener(csiListener);
  299. await use();
  300. clientInstrumentation.removeListener(csiListener);
  301. await artifactsRecorder.didFinishTest();
  302. }, { auto: "all-hooks-included", title: "trace recording", box: true, timeout: 0 }],
  303. _contextFactory: [async ({
  304. browser,
  305. video,
  306. _reuseContext,
  307. _combinedContextOptions
  308. /** mitigate dep-via-auto lack of traceability */
  309. }, use, testInfo) => {
  310. const testInfoImpl = testInfo;
  311. const videoMode = normalizeVideoMode(video);
  312. const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext;
  313. const contexts = /* @__PURE__ */ new Map();
  314. let counter = 0;
  315. await use(async (options) => {
  316. const hook = testInfoImpl._currentHookType();
  317. if (hook === "beforeAll" || hook === "afterAll") {
  318. throw new Error([
  319. `"context" and "page" fixtures are not supported in "${hook}" since they are created on a per-test basis.`,
  320. `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.`,
  321. `If you would like to configure your page before each test, do that in beforeEach hook instead.`
  322. ].join("\n"));
  323. }
  324. const videoOptions = captureVideo ? {
  325. recordVideo: {
  326. dir: tracing().artifactsDir(),
  327. size: typeof video === "string" ? void 0 : video.size
  328. }
  329. } : {};
  330. const context = await browser.newContext({ ...videoOptions, ...options });
  331. if (process.env.PW_CLOCK === "frozen") {
  332. await context._wrapApiCall(async () => {
  333. await context.clock.install({ time: 0 });
  334. await context.clock.pauseAt(1e3);
  335. }, { internal: true });
  336. } else if (process.env.PW_CLOCK === "realtime") {
  337. await context._wrapApiCall(async () => {
  338. await context.clock.install({ time: 0 });
  339. }, { internal: true });
  340. }
  341. let closed = false;
  342. const close = async () => {
  343. if (closed)
  344. return;
  345. closed = true;
  346. const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
  347. await context.close({ reason: closeReason });
  348. const testFailed = testInfo.status !== testInfo.expectedStatus;
  349. const preserveVideo = captureVideo && (videoMode === "on" || testFailed && videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1);
  350. if (preserveVideo) {
  351. const { pagesWithVideo: pagesForVideo } = contexts.get(context);
  352. const videos = pagesForVideo.map((p) => p.video()).filter((video2) => !!video2);
  353. await Promise.all(videos.map(async (v) => {
  354. try {
  355. const savedPath = testInfo.outputPath(`video${counter ? "-" + counter : ""}.webm`);
  356. ++counter;
  357. await v.saveAs(savedPath);
  358. testInfo.attachments.push({ name: "video", path: savedPath, contentType: "video/webm" });
  359. } catch (e) {
  360. }
  361. }));
  362. }
  363. };
  364. const contextData = { close, pagesWithVideo: [] };
  365. if (captureVideo)
  366. context.on("page", (page) => contextData.pagesWithVideo.push(page));
  367. contexts.set(context, contextData);
  368. return { context, close };
  369. });
  370. await Promise.all([...contexts.values()].map((data) => data.close()));
  371. }, { scope: "test", title: "context", box: true }],
  372. _optionContextReuseMode: ["none", { scope: "worker", option: true, box: true }],
  373. _optionConnectOptions: [void 0, { scope: "worker", option: true, box: true }],
  374. _reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
  375. let mode = _optionContextReuseMode;
  376. if (process.env.PW_TEST_REUSE_CONTEXT)
  377. mode = "when-possible";
  378. const reuse = mode === "when-possible" && normalizeVideoMode(video) === "off";
  379. await use(reuse);
  380. }, { scope: "worker", title: "context", box: true }],
  381. context: async ({ browser, _reuseContext, _contextFactory }, use, testInfo) => {
  382. const browserImpl = browser;
  383. attachConnectedHeaderIfNeeded(testInfo, browserImpl);
  384. if (!_reuseContext) {
  385. const { context: context2, close } = await _contextFactory();
  386. await use(context2);
  387. await close();
  388. return;
  389. }
  390. const context = await browserImpl._wrapApiCall(() => browserImpl._newContextForReuse(), { internal: true });
  391. await use(context);
  392. const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
  393. await browserImpl._wrapApiCall(() => browserImpl._disconnectFromReusedContext(closeReason), { internal: true });
  394. },
  395. page: async ({ context, _reuseContext }, use) => {
  396. if (!_reuseContext) {
  397. await use(await context.newPage());
  398. return;
  399. }
  400. let [page] = context.pages();
  401. if (!page)
  402. page = await context.newPage();
  403. await use(page);
  404. },
  405. request: async ({ playwright }, use) => {
  406. const request = await playwright.request.newContext();
  407. await use(request);
  408. const hook = test.info()._currentHookType();
  409. if (hook === "beforeAll") {
  410. await request.dispose({ reason: [
  411. `Fixture { request } from beforeAll cannot be reused in a test.`,
  412. ` - Recommended fix: use a separate { request } in the test.`,
  413. ` - Alternatively, manually create APIRequestContext in beforeAll and dispose it in afterAll.`,
  414. `See https://playwright.dev/docs/api-testing#sending-api-requests-from-ui-tests for more details.`
  415. ].join("\n") });
  416. } else {
  417. await request.dispose();
  418. }
  419. }
  420. };
  421. function normalizeVideoMode(video) {
  422. if (!video)
  423. return "off";
  424. let videoMode = typeof video === "string" ? video : video.mode;
  425. if (videoMode === "retry-with-video")
  426. videoMode = "on-first-retry";
  427. return videoMode;
  428. }
  429. function shouldCaptureVideo(videoMode, testInfo) {
  430. return videoMode === "on" || videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1;
  431. }
  432. function normalizeScreenshotMode(screenshot) {
  433. if (!screenshot)
  434. return "off";
  435. return typeof screenshot === "string" ? screenshot : screenshot.mode;
  436. }
  437. function attachConnectedHeaderIfNeeded(testInfo, browser) {
  438. const connectHeaders = browser?._connection.headers;
  439. if (!connectHeaders)
  440. return;
  441. for (const header of connectHeaders) {
  442. if (header.name !== "x-playwright-attachment")
  443. continue;
  444. const [name, value] = header.value.split("=");
  445. if (!name || !value)
  446. continue;
  447. if (testInfo.attachments.some((attachment) => attachment.name === name))
  448. continue;
  449. testInfo.attachments.push({ name, contentType: "text/plain", body: Buffer.from(value) });
  450. }
  451. }
  452. function resolveFileToConfig(file) {
  453. const config = test.info().config.configFile;
  454. if (!config || !file)
  455. return file;
  456. if (import_path.default.isAbsolute(file))
  457. return file;
  458. return import_path.default.resolve(import_path.default.dirname(config), file);
  459. }
  460. function resolveClientCerticates(clientCertificates) {
  461. for (const cert of clientCertificates) {
  462. cert.certPath = resolveFileToConfig(cert.certPath);
  463. cert.keyPath = resolveFileToConfig(cert.keyPath);
  464. cert.pfxPath = resolveFileToConfig(cert.pfxPath);
  465. }
  466. return clientCertificates;
  467. }
  468. const kTracingStarted = Symbol("kTracingStarted");
  469. function connectOptionsFromEnv() {
  470. const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT;
  471. if (!wsEndpoint)
  472. return void 0;
  473. const headers = process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : void 0;
  474. return {
  475. wsEndpoint,
  476. headers,
  477. exposeNetwork: process.env.PW_TEST_CONNECT_EXPOSE_NETWORK
  478. };
  479. }
  480. class SnapshotRecorder {
  481. constructor(_artifactsRecorder, _mode, _name, _contentType, _extension, _doSnapshot) {
  482. this._artifactsRecorder = _artifactsRecorder;
  483. this._mode = _mode;
  484. this._name = _name;
  485. this._contentType = _contentType;
  486. this._extension = _extension;
  487. this._doSnapshot = _doSnapshot;
  488. this._ordinal = 0;
  489. this._temporary = [];
  490. }
  491. fixOrdinal() {
  492. this._ordinal = this.testInfo.attachments.filter((a) => a.name === this._name).length;
  493. }
  494. shouldCaptureUponFinish() {
  495. return this._mode === "on" || this._mode === "only-on-failure" && this.testInfo._isFailure() || this._mode === "on-first-failure" && this.testInfo._isFailure() && this.testInfo.retry === 0;
  496. }
  497. async maybeCapture() {
  498. if (!this.shouldCaptureUponFinish())
  499. return;
  500. await Promise.all(this._artifactsRecorder._playwright._allPages().map((page) => this._snapshotPage(page, false)));
  501. }
  502. async persistTemporary() {
  503. if (this.shouldCaptureUponFinish()) {
  504. await Promise.all(this._temporary.map(async (file) => {
  505. try {
  506. const path2 = this._createAttachmentPath();
  507. await import_fs.default.promises.rename(file, path2);
  508. this._attach(path2);
  509. } catch {
  510. }
  511. }));
  512. }
  513. }
  514. async captureTemporary(context) {
  515. if (this._mode === "on" || this._mode === "only-on-failure" || this._mode === "on-first-failure" && this.testInfo.retry === 0)
  516. await Promise.all(context.pages().map((page) => this._snapshotPage(page, true)));
  517. }
  518. _attach(screenshotPath) {
  519. this.testInfo.attachments.push({ name: this._name, path: screenshotPath, contentType: this._contentType });
  520. }
  521. _createAttachmentPath() {
  522. const testFailed = this.testInfo._isFailure();
  523. const index = this._ordinal + 1;
  524. ++this._ordinal;
  525. const path2 = this.testInfo.outputPath(`test-${testFailed ? "failed" : "finished"}-${index}${this._extension}`);
  526. return path2;
  527. }
  528. _createTemporaryArtifact(...name) {
  529. const file = import_path.default.join(this._artifactsRecorder._artifactsDir, ...name);
  530. return file;
  531. }
  532. async _snapshotPage(page, temporary) {
  533. if (page[this.testInfo._uniqueSymbol])
  534. return;
  535. page[this.testInfo._uniqueSymbol] = true;
  536. try {
  537. const path2 = temporary ? this._createTemporaryArtifact((0, import_utils.createGuid)() + this._extension) : this._createAttachmentPath();
  538. await this._doSnapshot(page, path2);
  539. if (temporary)
  540. this._temporary.push(path2);
  541. else
  542. this._attach(path2);
  543. } catch {
  544. }
  545. }
  546. get testInfo() {
  547. return this._artifactsRecorder._testInfo;
  548. }
  549. }
  550. class ArtifactsRecorder {
  551. constructor(playwright, artifactsDir, screenshot) {
  552. this._playwright = playwright;
  553. this._artifactsDir = artifactsDir;
  554. const screenshotOptions = typeof screenshot === "string" ? void 0 : screenshot;
  555. this._startedCollectingArtifacts = Symbol("startedCollectingArtifacts");
  556. this._screenshotRecorder = new SnapshotRecorder(this, normalizeScreenshotMode(screenshot), "screenshot", "image/png", ".png", async (page, path2) => {
  557. await page._wrapApiCall(async () => {
  558. await page.screenshot({ ...screenshotOptions, timeout: 5e3, path: path2, caret: "initial" });
  559. }, { internal: true });
  560. });
  561. }
  562. async willStartTest(testInfo) {
  563. this._testInfo = testInfo;
  564. testInfo._onDidFinishTestFunction = () => this.didFinishTestFunction();
  565. this._screenshotRecorder.fixOrdinal();
  566. await Promise.all(this._playwright._allContexts().map((context) => this.didCreateBrowserContext(context)));
  567. const existingApiRequests = Array.from(this._playwright.request._contexts);
  568. await Promise.all(existingApiRequests.map((c) => this.didCreateRequestContext(c)));
  569. }
  570. async didCreateBrowserContext(context) {
  571. await this._startTraceChunkOnContextCreation(context, context.tracing);
  572. }
  573. async willCloseBrowserContext(context) {
  574. await this._stopTracing(context, context.tracing);
  575. await this._screenshotRecorder.captureTemporary(context);
  576. await this._takePageSnapshot(context);
  577. }
  578. async _takePageSnapshot(context) {
  579. if (process.env.PLAYWRIGHT_NO_COPY_PROMPT)
  580. return;
  581. if (this._testInfo.errors.length === 0)
  582. return;
  583. if (this._pageSnapshot)
  584. return;
  585. const page = context.pages()[0];
  586. if (!page)
  587. return;
  588. try {
  589. await page._wrapApiCall(async () => {
  590. this._pageSnapshot = await page._snapshotForAI({ timeout: 5e3 });
  591. }, { internal: true });
  592. } catch {
  593. }
  594. }
  595. async didCreateRequestContext(context) {
  596. await this._startTraceChunkOnContextCreation(context, context._tracing);
  597. }
  598. async willCloseRequestContext(context) {
  599. await this._stopTracing(context, context._tracing);
  600. }
  601. async didFinishTestFunction() {
  602. await this._screenshotRecorder.maybeCapture();
  603. }
  604. async didFinishTest() {
  605. await this.didFinishTestFunction();
  606. const leftoverContexts = this._playwright._allContexts();
  607. const leftoverApiRequests = Array.from(this._playwright.request._contexts);
  608. await Promise.all(leftoverContexts.map(async (context2) => {
  609. await this._stopTracing(context2, context2.tracing);
  610. }).concat(leftoverApiRequests.map(async (context2) => {
  611. await this._stopTracing(context2, context2._tracing);
  612. })));
  613. await this._screenshotRecorder.persistTemporary();
  614. const context = leftoverContexts[0];
  615. if (context)
  616. await this._takePageSnapshot(context);
  617. if (this._pageSnapshot && this._testInfo.errors.length > 0 && !this._testInfo.attachments.some((a) => a.name === "error-context")) {
  618. const lines = [
  619. "# Page snapshot",
  620. "",
  621. "```yaml",
  622. this._pageSnapshot,
  623. "```"
  624. ];
  625. const filePath = this._testInfo.outputPath("error-context.md");
  626. await import_fs.default.promises.writeFile(filePath, lines.join("\n"), "utf8");
  627. this._testInfo._attach({
  628. name: "error-context",
  629. contentType: "text/markdown",
  630. path: filePath
  631. }, void 0);
  632. }
  633. }
  634. async _startTraceChunkOnContextCreation(channelOwner, tracing2) {
  635. await channelOwner._wrapApiCall(async () => {
  636. const options = this._testInfo._tracing.traceOptions();
  637. if (options) {
  638. const title = this._testInfo._tracing.traceTitle();
  639. const name = this._testInfo._tracing.generateNextTraceRecordingName();
  640. if (!tracing2[kTracingStarted]) {
  641. await tracing2.start({ ...options, title, name });
  642. tracing2[kTracingStarted] = true;
  643. } else {
  644. await tracing2.startChunk({ title, name });
  645. }
  646. } else {
  647. if (tracing2[kTracingStarted]) {
  648. tracing2[kTracingStarted] = false;
  649. await tracing2.stop();
  650. }
  651. }
  652. }, { internal: true });
  653. }
  654. async _stopTracing(channelOwner, tracing2) {
  655. await channelOwner._wrapApiCall(async () => {
  656. if (tracing2[this._startedCollectingArtifacts])
  657. return;
  658. tracing2[this._startedCollectingArtifacts] = true;
  659. if (this._testInfo._tracing.traceOptions() && tracing2[kTracingStarted])
  660. await tracing2.stopChunk({ path: this._testInfo._tracing.maybeGenerateNextTraceRecordingPath() });
  661. }, { internal: true });
  662. }
  663. }
  664. function renderTitle(type, method, params, title) {
  665. const prefix = (0, import_utils.renderTitleForCall)({ title, type, method, params });
  666. let selector;
  667. if (params?.["selector"] && typeof params.selector === "string")
  668. selector = (0, import_utils.asLocatorDescription)("javascript", params.selector);
  669. return prefix + (selector ? ` ${selector}` : "");
  670. }
  671. function tracing() {
  672. return test.info()._tracing;
  673. }
  674. const test = _baseTest.extend(playwrightFixtures);
  675. // Annotate the CommonJS export names for ESM import in node:
  676. 0 && (module.exports = {
  677. _baseTest,
  678. defineConfig,
  679. expect,
  680. mergeExpects,
  681. mergeTests,
  682. test
  683. });