index.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports._baseTest = void 0;
  6. Object.defineProperty(exports, "_store", {
  7. enumerable: true,
  8. get: function () {
  9. return _store.store;
  10. }
  11. });
  12. Object.defineProperty(exports, "defineConfig", {
  13. enumerable: true,
  14. get: function () {
  15. return _configLoader.defineConfig;
  16. }
  17. });
  18. Object.defineProperty(exports, "expect", {
  19. enumerable: true,
  20. get: function () {
  21. return _expect.expect;
  22. }
  23. });
  24. Object.defineProperty(exports, "mergeExpects", {
  25. enumerable: true,
  26. get: function () {
  27. return _expect.mergeExpects;
  28. }
  29. });
  30. Object.defineProperty(exports, "mergeTests", {
  31. enumerable: true,
  32. get: function () {
  33. return _testType.mergeTests;
  34. }
  35. });
  36. exports.test = void 0;
  37. var fs = _interopRequireWildcard(require("fs"));
  38. var path = _interopRequireWildcard(require("path"));
  39. var playwrightLibrary = _interopRequireWildcard(require("playwright-core"));
  40. var _utils = require("playwright-core/lib/utils");
  41. var _testType = require("./common/testType");
  42. var _globals = require("./common/globals");
  43. var _testTracing = require("./worker/testTracing");
  44. var _expect = require("./matchers/expect");
  45. var _store = require("./store");
  46. var _configLoader = require("./common/configLoader");
  47. function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
  48. function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  49. /**
  50. * Copyright (c) Microsoft Corporation.
  51. *
  52. * Licensed under the Apache License, Version 2.0 (the 'License");
  53. * you may not use this file except in compliance with the License.
  54. * You may obtain a copy of the License at
  55. *
  56. * http://www.apache.org/licenses/LICENSE-2.0
  57. *
  58. * Unless required by applicable law or agreed to in writing, software
  59. * distributed under the License is distributed on an "AS IS" BASIS,
  60. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  61. * See the License for the specific language governing permissions and
  62. * limitations under the License.
  63. */
  64. const _baseTest = exports._baseTest = _testType.rootTestType.test;
  65. (0, _utils.addInternalStackPrefix)(path.dirname(require.resolve('../package.json')));
  66. if (process['__pw_initiator__']) {
  67. const originalStackTraceLimit = Error.stackTraceLimit;
  68. Error.stackTraceLimit = 200;
  69. try {
  70. throw new Error('Requiring @playwright/test second time, \nFirst:\n' + process['__pw_initiator__'] + '\n\nSecond: ');
  71. } finally {
  72. Error.stackTraceLimit = originalStackTraceLimit;
  73. }
  74. } else {
  75. process['__pw_initiator__'] = new Error().stack;
  76. }
  77. const playwrightFixtures = {
  78. defaultBrowserType: ['chromium', {
  79. scope: 'worker',
  80. option: true
  81. }],
  82. browserName: [({
  83. defaultBrowserType
  84. }, use) => use(defaultBrowserType), {
  85. scope: 'worker',
  86. option: true
  87. }],
  88. playwright: [async ({}, use) => {
  89. await use(require('playwright-core'));
  90. }, {
  91. scope: 'worker',
  92. _hideStep: true
  93. }],
  94. headless: [({
  95. launchOptions
  96. }, use) => {
  97. var _launchOptions$headle;
  98. return use((_launchOptions$headle = launchOptions.headless) !== null && _launchOptions$headle !== void 0 ? _launchOptions$headle : true);
  99. }, {
  100. scope: 'worker',
  101. option: true
  102. }],
  103. channel: [({
  104. launchOptions
  105. }, use) => use(launchOptions.channel), {
  106. scope: 'worker',
  107. option: true
  108. }],
  109. launchOptions: [{}, {
  110. scope: 'worker',
  111. option: true
  112. }],
  113. connectOptions: [async ({}, use) => {
  114. await use(connectOptionsFromEnv());
  115. }, {
  116. scope: 'worker',
  117. option: true
  118. }],
  119. screenshot: ['off', {
  120. scope: 'worker',
  121. option: true
  122. }],
  123. video: ['off', {
  124. scope: 'worker',
  125. option: true
  126. }],
  127. trace: ['off', {
  128. scope: 'worker',
  129. option: true
  130. }],
  131. _artifactsDir: [async ({}, use) => {
  132. await use(process.env.TEST_ARTIFACTS_DIR);
  133. }, {
  134. scope: 'worker',
  135. _title: 'playwright configuration'
  136. }],
  137. _browserOptions: [async ({
  138. playwright,
  139. headless,
  140. channel,
  141. launchOptions,
  142. _artifactsDir
  143. }, use) => {
  144. const options = {
  145. handleSIGINT: false,
  146. ...launchOptions
  147. };
  148. if (headless !== undefined) options.headless = headless;
  149. if (channel !== undefined) options.channel = channel;
  150. options.tracesDir = path.join(_artifactsDir, 'traces');
  151. for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) browserType._defaultLaunchOptions = options;
  152. await use(options);
  153. for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) browserType._defaultLaunchOptions = undefined;
  154. }, {
  155. scope: 'worker',
  156. auto: true
  157. }],
  158. browser: [async ({
  159. playwright,
  160. browserName,
  161. _browserOptions,
  162. connectOptions
  163. }, use, testInfo) => {
  164. if (!['chromium', 'firefox', 'webkit'].includes(browserName)) throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
  165. if (connectOptions) {
  166. var _connectOptions$expos;
  167. const browser = await playwright[browserName].connect({
  168. ...connectOptions,
  169. exposeNetwork: (_connectOptions$expos = connectOptions.exposeNetwork) !== null && _connectOptions$expos !== void 0 ? _connectOptions$expos : connectOptions._exposeNetwork,
  170. headers: {
  171. ...(process.env.PW_TEST_REUSE_CONTEXT ? {
  172. 'x-playwright-reuse-context': '1'
  173. } : {}),
  174. // HTTP headers are ASCII only (not UTF-8).
  175. 'x-playwright-launch-options': (0, _utils.jsonStringifyForceASCII)(_browserOptions),
  176. ...connectOptions.headers
  177. }
  178. });
  179. await use(browser);
  180. await browser._wrapApiCall(async () => {
  181. await browser.close({
  182. reason: 'Test ended.'
  183. });
  184. }, true);
  185. return;
  186. }
  187. const browser = await playwright[browserName].launch();
  188. await use(browser);
  189. await browser._wrapApiCall(async () => {
  190. await browser.close({
  191. reason: 'Test ended.'
  192. });
  193. }, true);
  194. }, {
  195. scope: 'worker',
  196. timeout: 0
  197. }],
  198. acceptDownloads: [({
  199. contextOptions
  200. }, use) => {
  201. var _contextOptions$accep;
  202. return use((_contextOptions$accep = contextOptions.acceptDownloads) !== null && _contextOptions$accep !== void 0 ? _contextOptions$accep : true);
  203. }, {
  204. option: true
  205. }],
  206. bypassCSP: [({
  207. contextOptions
  208. }, use) => {
  209. var _contextOptions$bypas;
  210. return use((_contextOptions$bypas = contextOptions.bypassCSP) !== null && _contextOptions$bypas !== void 0 ? _contextOptions$bypas : false);
  211. }, {
  212. option: true
  213. }],
  214. colorScheme: [({
  215. contextOptions
  216. }, use) => use(contextOptions.colorScheme === undefined ? 'light' : contextOptions.colorScheme), {
  217. option: true
  218. }],
  219. deviceScaleFactor: [({
  220. contextOptions
  221. }, use) => use(contextOptions.deviceScaleFactor), {
  222. option: true
  223. }],
  224. extraHTTPHeaders: [({
  225. contextOptions
  226. }, use) => use(contextOptions.extraHTTPHeaders), {
  227. option: true
  228. }],
  229. geolocation: [({
  230. contextOptions
  231. }, use) => use(contextOptions.geolocation), {
  232. option: true
  233. }],
  234. hasTouch: [({
  235. contextOptions
  236. }, use) => {
  237. var _contextOptions$hasTo;
  238. return use((_contextOptions$hasTo = contextOptions.hasTouch) !== null && _contextOptions$hasTo !== void 0 ? _contextOptions$hasTo : false);
  239. }, {
  240. option: true
  241. }],
  242. httpCredentials: [({
  243. contextOptions
  244. }, use) => use(contextOptions.httpCredentials), {
  245. option: true
  246. }],
  247. ignoreHTTPSErrors: [({
  248. contextOptions
  249. }, use) => {
  250. var _contextOptions$ignor;
  251. return use((_contextOptions$ignor = contextOptions.ignoreHTTPSErrors) !== null && _contextOptions$ignor !== void 0 ? _contextOptions$ignor : false);
  252. }, {
  253. option: true
  254. }],
  255. isMobile: [({
  256. contextOptions
  257. }, use) => {
  258. var _contextOptions$isMob;
  259. return use((_contextOptions$isMob = contextOptions.isMobile) !== null && _contextOptions$isMob !== void 0 ? _contextOptions$isMob : false);
  260. }, {
  261. option: true
  262. }],
  263. javaScriptEnabled: [({
  264. contextOptions
  265. }, use) => {
  266. var _contextOptions$javaS;
  267. return use((_contextOptions$javaS = contextOptions.javaScriptEnabled) !== null && _contextOptions$javaS !== void 0 ? _contextOptions$javaS : true);
  268. }, {
  269. option: true
  270. }],
  271. locale: [({
  272. contextOptions
  273. }, use) => {
  274. var _contextOptions$local;
  275. return use((_contextOptions$local = contextOptions.locale) !== null && _contextOptions$local !== void 0 ? _contextOptions$local : 'en-US');
  276. }, {
  277. option: true
  278. }],
  279. offline: [({
  280. contextOptions
  281. }, use) => {
  282. var _contextOptions$offli;
  283. return use((_contextOptions$offli = contextOptions.offline) !== null && _contextOptions$offli !== void 0 ? _contextOptions$offli : false);
  284. }, {
  285. option: true
  286. }],
  287. permissions: [({
  288. contextOptions
  289. }, use) => use(contextOptions.permissions), {
  290. option: true
  291. }],
  292. proxy: [({
  293. contextOptions
  294. }, use) => use(contextOptions.proxy), {
  295. option: true
  296. }],
  297. storageState: [({
  298. contextOptions
  299. }, use) => use(contextOptions.storageState), {
  300. option: true
  301. }],
  302. timezoneId: [({
  303. contextOptions
  304. }, use) => use(contextOptions.timezoneId), {
  305. option: true
  306. }],
  307. userAgent: [({
  308. contextOptions
  309. }, use) => use(contextOptions.userAgent), {
  310. option: true
  311. }],
  312. viewport: [({
  313. contextOptions
  314. }, use) => use(contextOptions.viewport === undefined ? {
  315. width: 1280,
  316. height: 720
  317. } : contextOptions.viewport), {
  318. option: true
  319. }],
  320. actionTimeout: [0, {
  321. option: true
  322. }],
  323. testIdAttribute: ['data-testid', {
  324. option: true
  325. }],
  326. navigationTimeout: [0, {
  327. option: true
  328. }],
  329. baseURL: [async ({}, use) => {
  330. await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
  331. }, {
  332. option: true
  333. }],
  334. serviceWorkers: [({
  335. contextOptions
  336. }, use) => {
  337. var _contextOptions$servi;
  338. return use((_contextOptions$servi = contextOptions.serviceWorkers) !== null && _contextOptions$servi !== void 0 ? _contextOptions$servi : 'allow');
  339. }, {
  340. option: true
  341. }],
  342. contextOptions: [{}, {
  343. option: true
  344. }],
  345. _combinedContextOptions: async ({
  346. acceptDownloads,
  347. bypassCSP,
  348. colorScheme,
  349. deviceScaleFactor,
  350. extraHTTPHeaders,
  351. hasTouch,
  352. geolocation,
  353. httpCredentials,
  354. ignoreHTTPSErrors,
  355. isMobile,
  356. javaScriptEnabled,
  357. locale,
  358. offline,
  359. permissions,
  360. proxy,
  361. storageState,
  362. viewport,
  363. timezoneId,
  364. userAgent,
  365. baseURL,
  366. contextOptions,
  367. serviceWorkers
  368. }, use) => {
  369. const options = {};
  370. if (acceptDownloads !== undefined) options.acceptDownloads = acceptDownloads;
  371. if (bypassCSP !== undefined) options.bypassCSP = bypassCSP;
  372. if (colorScheme !== undefined) options.colorScheme = colorScheme;
  373. if (deviceScaleFactor !== undefined) options.deviceScaleFactor = deviceScaleFactor;
  374. if (extraHTTPHeaders !== undefined) options.extraHTTPHeaders = extraHTTPHeaders;
  375. if (geolocation !== undefined) options.geolocation = geolocation;
  376. if (hasTouch !== undefined) options.hasTouch = hasTouch;
  377. if (httpCredentials !== undefined) options.httpCredentials = httpCredentials;
  378. if (ignoreHTTPSErrors !== undefined) options.ignoreHTTPSErrors = ignoreHTTPSErrors;
  379. if (isMobile !== undefined) options.isMobile = isMobile;
  380. if (javaScriptEnabled !== undefined) options.javaScriptEnabled = javaScriptEnabled;
  381. if (locale !== undefined) options.locale = locale;
  382. if (offline !== undefined) options.offline = offline;
  383. if (permissions !== undefined) options.permissions = permissions;
  384. if (proxy !== undefined) options.proxy = proxy;
  385. if (storageState !== undefined) options.storageState = storageState;
  386. if (timezoneId !== undefined) options.timezoneId = timezoneId;
  387. if (userAgent !== undefined) options.userAgent = userAgent;
  388. if (viewport !== undefined) options.viewport = viewport;
  389. if (baseURL !== undefined) options.baseURL = baseURL;
  390. if (serviceWorkers !== undefined) options.serviceWorkers = serviceWorkers;
  391. await use({
  392. ...contextOptions,
  393. ...options
  394. });
  395. },
  396. _setupContextOptions: [async ({
  397. playwright,
  398. _combinedContextOptions,
  399. _artifactsDir,
  400. actionTimeout,
  401. navigationTimeout,
  402. testIdAttribute
  403. }, use, testInfo) => {
  404. if (testIdAttribute) playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
  405. testInfo.snapshotSuffix = process.platform;
  406. if ((0, _utils.debugMode)()) testInfo.setTimeout(0);
  407. for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
  408. browserType._defaultContextOptions = _combinedContextOptions;
  409. browserType._defaultContextTimeout = actionTimeout || 0;
  410. browserType._defaultContextNavigationTimeout = navigationTimeout || 0;
  411. }
  412. playwright.request._defaultContextOptions = {
  413. ..._combinedContextOptions
  414. };
  415. playwright.request._defaultContextOptions.tracesDir = path.join(_artifactsDir, 'traces');
  416. playwright.request._defaultContextOptions.timeout = actionTimeout || 0;
  417. await use();
  418. playwright.request._defaultContextOptions = undefined;
  419. for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
  420. browserType._defaultContextOptions = undefined;
  421. browserType._defaultContextTimeout = undefined;
  422. browserType._defaultContextNavigationTimeout = undefined;
  423. }
  424. }, {
  425. auto: 'all-hooks-included',
  426. _title: 'context configuration'
  427. }],
  428. _setupArtifacts: [async ({
  429. playwright,
  430. _artifactsDir,
  431. trace,
  432. screenshot
  433. }, use, testInfo) => {
  434. const artifactsRecorder = new ArtifactsRecorder(playwright, _artifactsDir, trace, screenshot);
  435. await artifactsRecorder.willStartTest(testInfo);
  436. const csiListener = {
  437. onApiCallBegin: (apiName, params, frames, wallTime, userData) => {
  438. const testInfo = (0, _globals.currentTestInfo)();
  439. if (!testInfo || apiName.includes('setTestIdAttribute')) return {
  440. userObject: null
  441. };
  442. const step = testInfo._addStep({
  443. location: frames[0],
  444. category: 'pw:api',
  445. title: renderApiCall(apiName, params),
  446. apiName,
  447. params,
  448. wallTime,
  449. laxParent: true
  450. });
  451. userData.userObject = step;
  452. },
  453. onApiCallEnd: (userData, error) => {
  454. const step = userData.userObject;
  455. step === null || step === void 0 ? void 0 : step.complete({
  456. error
  457. });
  458. },
  459. onWillPause: () => {
  460. var _currentTestInfo;
  461. (_currentTestInfo = (0, _globals.currentTestInfo)()) === null || _currentTestInfo === void 0 ? void 0 : _currentTestInfo.setTimeout(0);
  462. },
  463. onDidCreateBrowserContext: async context => {
  464. await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.didCreateBrowserContext(context));
  465. const testInfo = (0, _globals.currentTestInfo)();
  466. if (testInfo) attachConnectedHeaderIfNeeded(testInfo, context.browser());
  467. },
  468. onDidCreateRequestContext: async context => {
  469. await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.didCreateRequestContext(context));
  470. },
  471. onWillCloseBrowserContext: async context => {
  472. await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.willCloseBrowserContext(context));
  473. },
  474. onWillCloseRequestContext: async context => {
  475. await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.willCloseRequestContext(context));
  476. }
  477. };
  478. const clientInstrumentation = playwright._instrumentation;
  479. clientInstrumentation.addListener(csiListener);
  480. await use();
  481. clientInstrumentation.removeListener(csiListener);
  482. await (artifactsRecorder === null || artifactsRecorder === void 0 ? void 0 : artifactsRecorder.didFinishTest());
  483. }, {
  484. auto: 'all-hooks-included',
  485. _title: 'trace recording'
  486. }],
  487. _contextFactory: [async ({
  488. browser,
  489. video,
  490. _artifactsDir,
  491. _reuseContext
  492. }, use, testInfo) => {
  493. const testInfoImpl = testInfo;
  494. const videoMode = normalizeVideoMode(video);
  495. const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext;
  496. const contexts = new Map();
  497. await use(async options => {
  498. const hook = hookType(testInfoImpl);
  499. if (hook) {
  500. 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'));
  501. }
  502. const videoOptions = captureVideo ? {
  503. recordVideo: {
  504. dir: _artifactsDir,
  505. size: typeof video === 'string' ? undefined : video.size
  506. }
  507. } : {};
  508. const context = await browser.newContext({
  509. ...videoOptions,
  510. ...options
  511. });
  512. const contextData = {
  513. pagesWithVideo: []
  514. };
  515. contexts.set(context, contextData);
  516. if (captureVideo) context.on('page', page => contextData.pagesWithVideo.push(page));
  517. return context;
  518. });
  519. let counter = 0;
  520. const closeReason = testInfo.status === 'timedOut' ? 'Test timeout of ' + testInfo.timeout + 'ms exceeded.' : 'Test ended.';
  521. await Promise.all([...contexts.keys()].map(async context => {
  522. context[kStartedContextTearDown] = true;
  523. await context._wrapApiCall(async () => {
  524. await context.close({
  525. reason: closeReason
  526. });
  527. }, true);
  528. const testFailed = testInfo.status !== testInfo.expectedStatus;
  529. const preserveVideo = captureVideo && (videoMode === 'on' || testFailed && videoMode === 'retain-on-failure' || videoMode === 'on-first-retry' && testInfo.retry === 1);
  530. if (preserveVideo) {
  531. const {
  532. pagesWithVideo: pagesForVideo
  533. } = contexts.get(context);
  534. const videos = pagesForVideo.map(p => p.video()).filter(Boolean);
  535. await Promise.all(videos.map(async v => {
  536. try {
  537. const savedPath = testInfo.outputPath(`video${counter ? '-' + counter : ''}.webm`);
  538. ++counter;
  539. await v.saveAs(savedPath);
  540. testInfo.attachments.push({
  541. name: 'video',
  542. path: savedPath,
  543. contentType: 'video/webm'
  544. });
  545. } catch (e) {
  546. // Silent catch empty videos.
  547. }
  548. }));
  549. }
  550. }));
  551. }, {
  552. scope: 'test',
  553. _title: 'context'
  554. }],
  555. _contextReuseMode: process.env.PW_TEST_REUSE_CONTEXT === 'when-possible' ? 'when-possible' : process.env.PW_TEST_REUSE_CONTEXT ? 'force' : 'none',
  556. _reuseContext: [async ({
  557. video,
  558. _contextReuseMode
  559. }, use, testInfo) => {
  560. const reuse = _contextReuseMode === 'force' || _contextReuseMode === 'when-possible' && !shouldCaptureVideo(normalizeVideoMode(video), testInfo);
  561. await use(reuse);
  562. }, {
  563. scope: 'test',
  564. _title: 'context'
  565. }],
  566. context: async ({
  567. playwright,
  568. browser,
  569. _reuseContext,
  570. _contextFactory
  571. }, use, testInfo) => {
  572. attachConnectedHeaderIfNeeded(testInfo, browser);
  573. if (!_reuseContext) {
  574. await use(await _contextFactory());
  575. return;
  576. }
  577. const defaultContextOptions = playwright.chromium._defaultContextOptions;
  578. const context = await browser._newContextForReuse(defaultContextOptions);
  579. context[kIsReusedContext] = true;
  580. await use(context);
  581. const closeReason = testInfo.status === 'timedOut' ? 'Test timeout of ' + testInfo.timeout + 'ms exceeded.' : 'Test ended.';
  582. await browser._stopPendingOperations(closeReason);
  583. },
  584. page: async ({
  585. context,
  586. _reuseContext
  587. }, use) => {
  588. if (!_reuseContext) {
  589. await use(await context.newPage());
  590. return;
  591. }
  592. // First time we are reusing the context, we should create the page.
  593. let [page] = context.pages();
  594. if (!page) page = await context.newPage();
  595. await use(page);
  596. },
  597. request: async ({
  598. playwright
  599. }, use) => {
  600. const request = await playwright.request.newContext();
  601. await use(request);
  602. request[kStartedContextTearDown] = true;
  603. await request.dispose();
  604. }
  605. };
  606. function hookType(testInfo) {
  607. const type = testInfo._timeoutManager.currentRunnableType();
  608. if (type === 'beforeAll' || type === 'afterAll') return type;
  609. }
  610. function normalizeVideoMode(video) {
  611. if (!video) return 'off';
  612. let videoMode = typeof video === 'string' ? video : video.mode;
  613. if (videoMode === 'retry-with-video') videoMode = 'on-first-retry';
  614. return videoMode;
  615. }
  616. function shouldCaptureVideo(videoMode, testInfo) {
  617. return videoMode === 'on' || videoMode === 'retain-on-failure' || videoMode === 'on-first-retry' && testInfo.retry === 1;
  618. }
  619. function normalizeTraceMode(trace) {
  620. if (!trace) return 'off';
  621. let traceMode = typeof trace === 'string' ? trace : trace.mode;
  622. if (traceMode === 'retry-with-trace') traceMode = 'on-first-retry';
  623. return traceMode;
  624. }
  625. function shouldCaptureTrace(traceMode, testInfo) {
  626. return traceMode === 'on' || traceMode === 'retain-on-failure' || traceMode === 'on-first-retry' && testInfo.retry === 1 || traceMode === 'on-all-retries' && testInfo.retry > 0;
  627. }
  628. function normalizeScreenshotMode(screenshot) {
  629. if (!screenshot) return 'off';
  630. return typeof screenshot === 'string' ? screenshot : screenshot.mode;
  631. }
  632. function attachConnectedHeaderIfNeeded(testInfo, browser) {
  633. const connectHeaders = browser === null || browser === void 0 ? void 0 : browser._connectHeaders;
  634. if (!connectHeaders) return;
  635. for (const header of connectHeaders) {
  636. if (header.name !== 'x-playwright-attachment') continue;
  637. const [name, value] = header.value.split('=');
  638. if (!name || !value) continue;
  639. if (testInfo.attachments.some(attachment => attachment.name === name)) continue;
  640. testInfo.attachments.push({
  641. name,
  642. contentType: 'text/plain',
  643. body: Buffer.from(value)
  644. });
  645. }
  646. }
  647. const kTracingStarted = Symbol('kTracingStarted');
  648. const kIsReusedContext = Symbol('kReusedContext');
  649. const kStartedContextTearDown = Symbol('kStartedContextTearDown');
  650. let traceOrdinal = 0;
  651. function connectOptionsFromEnv() {
  652. const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT;
  653. if (!wsEndpoint) return undefined;
  654. const headers = process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : undefined;
  655. return {
  656. wsEndpoint,
  657. headers,
  658. exposeNetwork: process.env.PW_TEST_CONNECT_EXPOSE_NETWORK
  659. };
  660. }
  661. class ArtifactsRecorder {
  662. constructor(playwright, artifactsDir, trace, screenshot) {
  663. this._testInfo = void 0;
  664. this._playwright = void 0;
  665. this._artifactsDir = void 0;
  666. this._screenshotMode = void 0;
  667. this._traceMode = void 0;
  668. this._captureTrace = false;
  669. this._screenshotOptions = void 0;
  670. this._traceOptions = void 0;
  671. this._temporaryTraceFiles = [];
  672. this._temporaryScreenshots = [];
  673. this._temporaryArtifacts = [];
  674. this._reusedContexts = new Set();
  675. this._screenshotOrdinal = 0;
  676. this._screenshottedSymbol = void 0;
  677. this._startedCollectingArtifacts = void 0;
  678. this._playwright = playwright;
  679. this._artifactsDir = artifactsDir;
  680. this._screenshotMode = normalizeScreenshotMode(screenshot);
  681. this._screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot;
  682. this._traceMode = normalizeTraceMode(trace);
  683. const defaultTraceOptions = {
  684. screenshots: true,
  685. snapshots: true,
  686. sources: true,
  687. attachments: true,
  688. _live: false
  689. };
  690. this._traceOptions = typeof trace === 'string' ? defaultTraceOptions : {
  691. ...defaultTraceOptions,
  692. ...trace,
  693. mode: undefined
  694. };
  695. this._screenshottedSymbol = Symbol('screenshotted');
  696. this._startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
  697. }
  698. _createTemporaryArtifact(...name) {
  699. const file = path.join(this._artifactsDir, ...name);
  700. this._temporaryArtifacts.push(file);
  701. return file;
  702. }
  703. async willStartTest(testInfo) {
  704. this._testInfo = testInfo;
  705. testInfo._onDidFinishTestFunction = () => this.didFinishTestFunction();
  706. this._captureTrace = shouldCaptureTrace(this._traceMode, testInfo) && !process.env.PW_TEST_DISABLE_TRACING;
  707. if (this._captureTrace) this._testInfo._tracing.start(this._createTemporaryArtifact('traces', `${this._testInfo.testId}-test.trace`), this._traceOptions);
  708. // Since beforeAll(s), test and afterAll(s) reuse the same TestInfo, make sure we do not
  709. // overwrite previous screenshots.
  710. this._screenshotOrdinal = testInfo.attachments.filter(a => a.name === 'screenshot').length;
  711. // Process existing contexts.
  712. for (const browserType of [this._playwright.chromium, this._playwright.firefox, this._playwright.webkit]) {
  713. const promises = [];
  714. const existingContexts = Array.from(browserType._contexts);
  715. for (const context of existingContexts) {
  716. if (context[kIsReusedContext]) this._reusedContexts.add(context);else promises.push(this.didCreateBrowserContext(context));
  717. }
  718. await Promise.all(promises);
  719. }
  720. {
  721. const existingApiRequests = Array.from(this._playwright.request._contexts);
  722. await Promise.all(existingApiRequests.map(c => this.didCreateRequestContext(c)));
  723. }
  724. }
  725. async didCreateBrowserContext(context) {
  726. await this._startTraceChunkOnContextCreation(context.tracing);
  727. }
  728. async willCloseBrowserContext(context) {
  729. // When reusing context, we get all previous contexts closed at the start of next test.
  730. // Do not record empty traces and useless screenshots for them.
  731. if (this._reusedContexts.has(context)) return;
  732. await this._stopTracing(context.tracing, context[kStartedContextTearDown]);
  733. if (this._screenshotMode === 'on' || this._screenshotMode === 'only-on-failure') {
  734. // Capture screenshot for now. We'll know whether we have to preserve them
  735. // after the test finishes.
  736. await Promise.all(context.pages().map(page => this._screenshotPage(page, true)));
  737. }
  738. }
  739. async didCreateRequestContext(context) {
  740. const tracing = context._tracing;
  741. await this._startTraceChunkOnContextCreation(tracing);
  742. }
  743. async willCloseRequestContext(context) {
  744. const tracing = context._tracing;
  745. await this._stopTracing(tracing, context[kStartedContextTearDown]);
  746. }
  747. async didFinishTestFunction() {
  748. const captureScreenshots = this._screenshotMode === 'on' || this._screenshotMode === 'only-on-failure' && this._testInfo._isFailure();
  749. if (captureScreenshots) await this._screenshotOnTestFailure();
  750. }
  751. async didFinishTest() {
  752. const captureScreenshots = this._screenshotMode === 'on' || this._screenshotMode === 'only-on-failure' && this._testInfo._isFailure();
  753. if (captureScreenshots) await this._screenshotOnTestFailure();
  754. const leftoverContexts = [];
  755. for (const browserType of [this._playwright.chromium, this._playwright.firefox, this._playwright.webkit]) leftoverContexts.push(...browserType._contexts);
  756. const leftoverApiRequests = Array.from(this._playwright.request._contexts);
  757. // Collect traces/screenshots for remaining contexts.
  758. await Promise.all(leftoverContexts.map(async context => {
  759. await this._stopTracing(context.tracing, true);
  760. }).concat(leftoverApiRequests.map(async context => {
  761. const tracing = context._tracing;
  762. await this._stopTracing(tracing, true);
  763. })));
  764. // Attach temporary screenshots for contexts closed before collecting the test trace.
  765. if (captureScreenshots) {
  766. for (const file of this._temporaryScreenshots) {
  767. try {
  768. const screenshotPath = this._createScreenshotAttachmentPath();
  769. await fs.promises.rename(file, screenshotPath);
  770. this._attachScreenshot(screenshotPath);
  771. } catch {}
  772. }
  773. }
  774. // Collect test trace.
  775. if (this._preserveTrace()) {
  776. const tracePath = this._createTemporaryArtifact((0, _utils.createGuid)() + '.zip');
  777. this._temporaryTraceFiles.push(tracePath);
  778. await this._testInfo._tracing.stop(tracePath);
  779. }
  780. // Either remove or attach temporary traces for contexts closed before the
  781. // test has finished.
  782. if (this._preserveTrace() && this._temporaryTraceFiles.length) {
  783. const tracePath = this._testInfo.outputPath(`trace.zip`);
  784. // This could be: beforeHooks, or beforeHooks + test, etc.
  785. const beforeHooksHadTrace = fs.existsSync(tracePath);
  786. if (beforeHooksHadTrace) {
  787. await fs.promises.rename(tracePath, tracePath + '.tmp');
  788. this._temporaryTraceFiles.unshift(tracePath + '.tmp');
  789. }
  790. await (0, _testTracing.mergeTraceFiles)(tracePath, this._temporaryTraceFiles);
  791. // Do not add attachment twice.
  792. if (!beforeHooksHadTrace) this._testInfo.attachments.push({
  793. name: 'trace',
  794. path: tracePath,
  795. contentType: 'application/zip'
  796. });
  797. }
  798. for (const file of this._temporaryArtifacts) await fs.promises.unlink(file).catch(() => {});
  799. }
  800. _createScreenshotAttachmentPath() {
  801. const testFailed = this._testInfo._isFailure();
  802. const index = this._screenshotOrdinal + 1;
  803. ++this._screenshotOrdinal;
  804. const screenshotPath = this._testInfo.outputPath(`test-${testFailed ? 'failed' : 'finished'}-${index}.png`);
  805. return screenshotPath;
  806. }
  807. async _screenshotPage(page, temporary) {
  808. if (page[this._screenshottedSymbol]) return;
  809. page[this._screenshottedSymbol] = true;
  810. try {
  811. const screenshotPath = temporary ? this._createTemporaryArtifact((0, _utils.createGuid)() + '.png') : this._createScreenshotAttachmentPath();
  812. // Pass caret=initial to avoid any evaluations that might slow down the screenshot
  813. // and let the page modify itself from the problematic state it had at the moment of failure.
  814. await page.screenshot({
  815. ...this._screenshotOptions,
  816. timeout: 5000,
  817. path: screenshotPath,
  818. caret: 'initial'
  819. });
  820. if (temporary) this._temporaryScreenshots.push(screenshotPath);else this._attachScreenshot(screenshotPath);
  821. } catch {
  822. // Screenshot may fail, just ignore.
  823. }
  824. }
  825. _attachScreenshot(screenshotPath) {
  826. this._testInfo.attachments.push({
  827. name: 'screenshot',
  828. path: screenshotPath,
  829. contentType: 'image/png'
  830. });
  831. }
  832. async _screenshotOnTestFailure() {
  833. const contexts = [];
  834. for (const browserType of [this._playwright.chromium, this._playwright.firefox, this._playwright.webkit]) contexts.push(...browserType._contexts);
  835. const pages = contexts.map(ctx => ctx.pages()).flat();
  836. await Promise.all(pages.map(page => this._screenshotPage(page, false)));
  837. }
  838. async _startTraceChunkOnContextCreation(tracing) {
  839. if (this._captureTrace) {
  840. const title = [path.relative(this._testInfo.project.testDir, this._testInfo.file) + ':' + this._testInfo.line, ...this._testInfo.titlePath.slice(1)].join(' › ');
  841. const ordinalSuffix = traceOrdinal ? `-context${traceOrdinal}` : '';
  842. ++traceOrdinal;
  843. const retrySuffix = this._testInfo.retry ? `-retry${this._testInfo.retry}` : '';
  844. // Note that trace name must start with testId for live tracing to work.
  845. const name = `${this._testInfo.testId}${retrySuffix}${ordinalSuffix}`;
  846. if (!tracing[kTracingStarted]) {
  847. await tracing.start({
  848. ...this._traceOptions,
  849. title,
  850. name
  851. });
  852. tracing[kTracingStarted] = true;
  853. } else {
  854. await tracing.startChunk({
  855. title,
  856. name
  857. });
  858. }
  859. } else {
  860. if (tracing[kTracingStarted]) {
  861. tracing[kTracingStarted] = false;
  862. await tracing.stop();
  863. }
  864. }
  865. }
  866. _preserveTrace() {
  867. const testFailed = this._testInfo.status !== this._testInfo.expectedStatus;
  868. return this._captureTrace && (this._traceMode === 'on' || testFailed && this._traceMode === 'retain-on-failure' || this._traceMode === 'on-first-retry' && this._testInfo.retry === 1 || this._traceMode === 'on-all-retries' && this._testInfo.retry > 0);
  869. }
  870. async _stopTracing(tracing, contextTearDownStarted) {
  871. if (tracing[this._startedCollectingArtifacts]) return;
  872. tracing[this._startedCollectingArtifacts] = true;
  873. if (this._captureTrace) {
  874. let tracePath;
  875. // Create a trace file if we know that:
  876. // - it is's going to be used due to the config setting and the test status or
  877. // - we are inside a test or afterEach and the user manually closed the context.
  878. if (this._preserveTrace() || !contextTearDownStarted) {
  879. tracePath = this._createTemporaryArtifact((0, _utils.createGuid)() + '.zip');
  880. this._temporaryTraceFiles.push(tracePath);
  881. }
  882. await tracing.stopChunk({
  883. path: tracePath
  884. });
  885. }
  886. }
  887. }
  888. const paramsToRender = ['url', 'selector', 'text', 'key'];
  889. function renderApiCall(apiName, params) {
  890. const paramsArray = [];
  891. if (params) {
  892. for (const name of paramsToRender) {
  893. if (!(name in params)) continue;
  894. let value;
  895. if (name === 'selector' && (0, _utils.isString)(params[name]) && params[name].startsWith('internal:')) {
  896. const getter = (0, _utils.asLocator)('javascript', params[name]);
  897. apiName = apiName.replace(/^locator\./, 'locator.' + getter + '.');
  898. apiName = apiName.replace(/^page\./, 'page.' + getter + '.');
  899. apiName = apiName.replace(/^frame\./, 'frame.' + getter + '.');
  900. } else {
  901. value = params[name];
  902. paramsArray.push(value);
  903. }
  904. }
  905. }
  906. const paramsText = paramsArray.length ? '(' + paramsArray.join(', ') + ')' : '';
  907. return apiName + paramsText;
  908. }
  909. const test = exports.test = _baseTest.extend(playwrightFixtures);