expect.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.expect = void 0;
  6. exports.mergeExpects = mergeExpects;
  7. exports.printReceivedStringContainExpectedSubstring = exports.printReceivedStringContainExpectedResult = void 0;
  8. var _utils = require("playwright-core/lib/utils");
  9. var _matchers = require("./matchers");
  10. var _toMatchSnapshot = require("./toMatchSnapshot");
  11. var _globals = require("../common/globals");
  12. var _util = require("../util");
  13. var _expectBundle = require("../common/expectBundle");
  14. var _testInfo = require("../worker/testInfo");
  15. var _matcherHint = require("./matcherHint");
  16. /**
  17. * Copyright Microsoft Corporation. All rights reserved.
  18. *
  19. * Licensed under the Apache License, Version 2.0 (the "License");
  20. * you may not use this file except in compliance with the License.
  21. * You may obtain a copy of the License at
  22. *
  23. * http://www.apache.org/licenses/LICENSE-2.0
  24. *
  25. * Unless required by applicable law or agreed to in writing, software
  26. * distributed under the License is distributed on an "AS IS" BASIS,
  27. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28. * See the License for the specific language governing permissions and
  29. * limitations under the License.
  30. */
  31. // #region
  32. // Mirrored from https://github.com/facebook/jest/blob/f13abff8df9a0e1148baf3584bcde6d1b479edc7/packages/expect/src/print.ts
  33. /**
  34. * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  35. *
  36. * This source code is licensed under the MIT license found here
  37. * https://github.com/facebook/jest/blob/1547740bbc26400d69f4576bf35645163e942829/LICENSE
  38. */
  39. // Format substring but do not enclose in double quote marks.
  40. // The replacement is compatible with pretty-format package.
  41. const printSubstring = val => val.replace(/"|\\/g, '\\$&');
  42. const printReceivedStringContainExpectedSubstring = (received, start, length // not end
  43. ) => (0, _expectBundle.RECEIVED_COLOR)('"' + printSubstring(received.slice(0, start)) + (0, _expectBundle.INVERTED_COLOR)(printSubstring(received.slice(start, start + length))) + printSubstring(received.slice(start + length)) + '"');
  44. exports.printReceivedStringContainExpectedSubstring = printReceivedStringContainExpectedSubstring;
  45. const printReceivedStringContainExpectedResult = (received, result) => result === null ? (0, _expectBundle.printReceived)(received) : printReceivedStringContainExpectedSubstring(received, result.index, result[0].length);
  46. // #endregion
  47. exports.printReceivedStringContainExpectedResult = printReceivedStringContainExpectedResult;
  48. function createMatchers(actual, info) {
  49. return new Proxy((0, _expectBundle.expect)(actual), new ExpectMetaInfoProxyHandler(info));
  50. }
  51. function createExpect(info) {
  52. const expectInstance = new Proxy(_expectBundle.expect, {
  53. apply: function (target, thisArg, argumentsList) {
  54. const [actual, messageOrOptions] = argumentsList;
  55. const message = (0, _utils.isString)(messageOrOptions) ? messageOrOptions : (messageOrOptions === null || messageOrOptions === void 0 ? void 0 : messageOrOptions.message) || info.message;
  56. const newInfo = {
  57. ...info,
  58. message
  59. };
  60. if (newInfo.isPoll) {
  61. if (typeof actual !== 'function') throw new Error('`expect.poll()` accepts only function as a first argument');
  62. newInfo.generator = actual;
  63. }
  64. return createMatchers(actual, newInfo);
  65. },
  66. get: function (target, property) {
  67. if (property === 'configure') return configure;
  68. if (property === 'extend') {
  69. return matchers => {
  70. _expectBundle.expect.extend(matchers);
  71. return expectInstance;
  72. };
  73. }
  74. if (property === 'soft') {
  75. return (actual, messageOrOptions) => {
  76. return configure({
  77. soft: true
  78. })(actual, messageOrOptions);
  79. };
  80. }
  81. if (property === 'poll') {
  82. return (actual, messageOrOptions) => {
  83. const poll = (0, _utils.isString)(messageOrOptions) ? {} : messageOrOptions || {};
  84. return configure({
  85. _poll: poll
  86. })(actual, messageOrOptions);
  87. };
  88. }
  89. return _expectBundle.expect[property];
  90. }
  91. });
  92. const configure = configuration => {
  93. const newInfo = {
  94. ...info
  95. };
  96. if ('message' in configuration) newInfo.message = configuration.message;
  97. if ('timeout' in configuration) newInfo.timeout = configuration.timeout;
  98. if ('soft' in configuration) newInfo.isSoft = configuration.soft;
  99. if ('_poll' in configuration) {
  100. newInfo.isPoll = !!configuration._poll;
  101. if (typeof configuration._poll === 'object') {
  102. newInfo.pollTimeout = configuration._poll.timeout;
  103. newInfo.pollIntervals = configuration._poll.intervals;
  104. }
  105. }
  106. return createExpect(newInfo);
  107. };
  108. return expectInstance;
  109. }
  110. const expect = exports.expect = createExpect({});
  111. _expectBundle.expect.setState({
  112. expand: false
  113. });
  114. const customAsyncMatchers = {
  115. toBeAttached: _matchers.toBeAttached,
  116. toBeChecked: _matchers.toBeChecked,
  117. toBeDisabled: _matchers.toBeDisabled,
  118. toBeEditable: _matchers.toBeEditable,
  119. toBeEmpty: _matchers.toBeEmpty,
  120. toBeEnabled: _matchers.toBeEnabled,
  121. toBeFocused: _matchers.toBeFocused,
  122. toBeHidden: _matchers.toBeHidden,
  123. toBeInViewport: _matchers.toBeInViewport,
  124. toBeOK: _matchers.toBeOK,
  125. toBeVisible: _matchers.toBeVisible,
  126. toContainText: _matchers.toContainText,
  127. toHaveAttribute: _matchers.toHaveAttribute,
  128. toHaveClass: _matchers.toHaveClass,
  129. toHaveCount: _matchers.toHaveCount,
  130. toHaveCSS: _matchers.toHaveCSS,
  131. toHaveId: _matchers.toHaveId,
  132. toHaveJSProperty: _matchers.toHaveJSProperty,
  133. toHaveText: _matchers.toHaveText,
  134. toHaveTitle: _matchers.toHaveTitle,
  135. toHaveURL: _matchers.toHaveURL,
  136. toHaveValue: _matchers.toHaveValue,
  137. toHaveValues: _matchers.toHaveValues,
  138. toHaveScreenshot: _toMatchSnapshot.toHaveScreenshot,
  139. toPass: _matchers.toPass
  140. };
  141. const customMatchers = {
  142. ...customAsyncMatchers,
  143. toMatchSnapshot: _toMatchSnapshot.toMatchSnapshot
  144. };
  145. class ExpectMetaInfoProxyHandler {
  146. constructor(info) {
  147. this._info = void 0;
  148. this._info = {
  149. ...info
  150. };
  151. }
  152. get(target, matcherName, receiver) {
  153. let matcher = Reflect.get(target, matcherName, receiver);
  154. if (typeof matcherName !== 'string') return matcher;
  155. if (matcher === undefined) throw new Error(`expect: Property '${matcherName}' not found.`);
  156. if (typeof matcher !== 'function') {
  157. if (matcherName === 'not') this._info.isNot = !this._info.isNot;
  158. return new Proxy(matcher, this);
  159. }
  160. if (this._info.isPoll) {
  161. if (customAsyncMatchers[matcherName] || matcherName === 'resolves' || matcherName === 'rejects') throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`);
  162. matcher = (...args) => pollMatcher(matcherName, !!this._info.isNot, this._info.pollIntervals, (0, _globals.currentExpectTimeout)({
  163. timeout: this._info.pollTimeout
  164. }), this._info.generator, ...args);
  165. }
  166. return (...args) => {
  167. const testInfo = (0, _globals.currentTestInfo)();
  168. // We assume that the matcher will read the current expect timeout the first thing.
  169. (0, _globals.setCurrentExpectConfigureTimeout)(this._info.timeout);
  170. if (!testInfo) return matcher.call(target, ...args);
  171. const customMessage = this._info.message || '';
  172. const argsSuffix = computeArgsSuffix(matcherName, args);
  173. const defaultTitle = `expect${this._info.isPoll ? '.poll' : ''}${this._info.isSoft ? '.soft' : ''}${this._info.isNot ? '.not' : ''}.${matcherName}${argsSuffix}`;
  174. const title = customMessage || defaultTitle;
  175. const wallTime = Date.now();
  176. // This looks like it is unnecessary, but it isn't - we need to filter
  177. // out all the frames that belong to the test runner from caught runtime errors.
  178. const rawStack = (0, _utils.captureRawStack)();
  179. const stackFrames = (0, _util.filteredStackTrace)(rawStack);
  180. // Enclose toPass in a step to maintain async stacks, toPass matcher is always async.
  181. const stepInfo = {
  182. category: 'expect',
  183. title: (0, _util.trimLongString)(title, 1024),
  184. params: args[0] ? {
  185. expected: args[0]
  186. } : undefined,
  187. wallTime,
  188. infectParentStepsWithError: this._info.isSoft,
  189. laxParent: true,
  190. isSoft: this._info.isSoft
  191. };
  192. const step = testInfo._addStep(stepInfo);
  193. const reportStepError = jestError => {
  194. const error = new _matcherHint.ExpectError(jestError, customMessage, stackFrames);
  195. step.complete({
  196. error
  197. });
  198. if (!this._info.isSoft) throw error;
  199. };
  200. const finalizer = () => {
  201. step.complete({});
  202. };
  203. try {
  204. const expectZone = matcherName !== 'toPass' ? {
  205. title,
  206. wallTime
  207. } : null;
  208. const callback = () => matcher.call(target, ...args);
  209. const result = expectZone ? _utils.zones.run('expectZone', expectZone, callback) : _utils.zones.preserve(callback);
  210. if (result instanceof Promise) return result.then(finalizer).catch(reportStepError);
  211. finalizer();
  212. return result;
  213. } catch (e) {
  214. reportStepError(e);
  215. }
  216. };
  217. }
  218. }
  219. async function pollMatcher(matcherName, isNot, pollIntervals, timeout, generator, ...args) {
  220. const testInfo = (0, _globals.currentTestInfo)();
  221. const {
  222. deadline,
  223. timeoutMessage
  224. } = testInfo ? testInfo._deadlineForMatcher(timeout) : _testInfo.TestInfoImpl._defaultDeadlineForMatcher(timeout);
  225. const result = await (0, _utils.pollAgainstDeadline)(async () => {
  226. if (testInfo && (0, _globals.currentTestInfo)() !== testInfo) return {
  227. continuePolling: false,
  228. result: undefined
  229. };
  230. const value = await generator();
  231. let expectInstance = (0, _expectBundle.expect)(value);
  232. if (isNot) expectInstance = expectInstance.not;
  233. try {
  234. expectInstance[matcherName].call(expectInstance, ...args);
  235. return {
  236. continuePolling: false,
  237. result: undefined
  238. };
  239. } catch (error) {
  240. return {
  241. continuePolling: true,
  242. result: error
  243. };
  244. }
  245. }, deadline, pollIntervals !== null && pollIntervals !== void 0 ? pollIntervals : [100, 250, 500, 1000]);
  246. if (result.timedOut) {
  247. const message = result.result ? [result.result.message, '', `Call Log:`, `- ${timeoutMessage}`].join('\n') : timeoutMessage;
  248. throw new Error(message);
  249. }
  250. }
  251. function computeArgsSuffix(matcherName, args) {
  252. let value = '';
  253. if (matcherName === 'toHaveScreenshot') value = (0, _toMatchSnapshot.toHaveScreenshotStepTitle)(...args);
  254. return value ? `(${value})` : '';
  255. }
  256. _expectBundle.expect.extend(customMatchers);
  257. function mergeExpects(...expects) {
  258. return expect;
  259. }