index.js 29 KB

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