expect.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. "use strict";
  2. var __defProp = Object.defineProperty;
  3. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  4. var __getOwnPropNames = Object.getOwnPropertyNames;
  5. var __hasOwnProp = Object.prototype.hasOwnProperty;
  6. var __export = (target, all) => {
  7. for (var name in all)
  8. __defProp(target, name, { get: all[name], enumerable: true });
  9. };
  10. var __copyProps = (to, from, except, desc) => {
  11. if (from && typeof from === "object" || typeof from === "function") {
  12. for (let key of __getOwnPropNames(from))
  13. if (!__hasOwnProp.call(to, key) && key !== except)
  14. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  15. }
  16. return to;
  17. };
  18. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  19. var expect_exports = {};
  20. __export(expect_exports, {
  21. expect: () => expect,
  22. mergeExpects: () => mergeExpects,
  23. printReceivedStringContainExpectedResult: () => printReceivedStringContainExpectedResult,
  24. printReceivedStringContainExpectedSubstring: () => printReceivedStringContainExpectedSubstring
  25. });
  26. module.exports = __toCommonJS(expect_exports);
  27. var import_utils = require("playwright-core/lib/utils");
  28. var import_matcherHint = require("./matcherHint");
  29. var import_matchers = require("./matchers");
  30. var import_toMatchAriaSnapshot = require("./toMatchAriaSnapshot");
  31. var import_toMatchSnapshot = require("./toMatchSnapshot");
  32. var import_expectBundle = require("../common/expectBundle");
  33. var import_globals = require("../common/globals");
  34. var import_util = require("../util");
  35. var import_testInfo = require("../worker/testInfo");
  36. const printSubstring = (val) => val.replace(/"|\\/g, "\\$&");
  37. const printReceivedStringContainExpectedSubstring = (received, start, length) => (0, import_expectBundle.RECEIVED_COLOR)(
  38. '"' + printSubstring(received.slice(0, start)) + (0, import_expectBundle.INVERTED_COLOR)(printSubstring(received.slice(start, start + length))) + printSubstring(received.slice(start + length)) + '"'
  39. );
  40. const printReceivedStringContainExpectedResult = (received, result) => result === null ? (0, import_expectBundle.printReceived)(received) : printReceivedStringContainExpectedSubstring(
  41. received,
  42. result.index,
  43. result[0].length
  44. );
  45. function createMatchers(actual, info, prefix) {
  46. return new Proxy((0, import_expectBundle.expect)(actual), new ExpectMetaInfoProxyHandler(info, prefix));
  47. }
  48. const userMatchersSymbol = Symbol("userMatchers");
  49. function qualifiedMatcherName(qualifier, matcherName) {
  50. return qualifier.join(":") + "$" + matcherName;
  51. }
  52. function createExpect(info, prefix, userMatchers) {
  53. const expectInstance = new Proxy(import_expectBundle.expect, {
  54. apply: function(target, thisArg, argumentsList) {
  55. const [actual, messageOrOptions] = argumentsList;
  56. const message = (0, import_utils.isString)(messageOrOptions) ? messageOrOptions : messageOrOptions?.message || info.message;
  57. const newInfo = { ...info, message };
  58. if (newInfo.poll) {
  59. if (typeof actual !== "function")
  60. throw new Error("`expect.poll()` accepts only function as a first argument");
  61. newInfo.poll.generator = actual;
  62. }
  63. return createMatchers(actual, newInfo, prefix);
  64. },
  65. get: function(target, property) {
  66. if (property === "configure")
  67. return configure;
  68. if (property === "extend") {
  69. return (matchers) => {
  70. const qualifier = [...prefix, (0, import_utils.createGuid)()];
  71. const wrappedMatchers = {};
  72. for (const [name, matcher] of Object.entries(matchers)) {
  73. wrappedMatchers[name] = wrapPlaywrightMatcherToPassNiceThis(matcher);
  74. const key = qualifiedMatcherName(qualifier, name);
  75. wrappedMatchers[key] = wrappedMatchers[name];
  76. Object.defineProperty(wrappedMatchers[key], "name", { value: name });
  77. }
  78. import_expectBundle.expect.extend(wrappedMatchers);
  79. return createExpect(info, qualifier, { ...userMatchers, ...matchers });
  80. };
  81. }
  82. if (property === "soft") {
  83. return (actual, messageOrOptions) => {
  84. return configure({ soft: true })(actual, messageOrOptions);
  85. };
  86. }
  87. if (property === userMatchersSymbol)
  88. return userMatchers;
  89. if (property === "poll") {
  90. return (actual, messageOrOptions) => {
  91. const poll = (0, import_utils.isString)(messageOrOptions) ? {} : messageOrOptions || {};
  92. return configure({ _poll: poll })(actual, messageOrOptions);
  93. };
  94. }
  95. return import_expectBundle.expect[property];
  96. }
  97. });
  98. const configure = (configuration) => {
  99. const newInfo = { ...info };
  100. if ("message" in configuration)
  101. newInfo.message = configuration.message;
  102. if ("timeout" in configuration)
  103. newInfo.timeout = configuration.timeout;
  104. if ("soft" in configuration)
  105. newInfo.isSoft = configuration.soft;
  106. if ("_poll" in configuration) {
  107. newInfo.poll = configuration._poll ? { ...info.poll, generator: () => {
  108. } } : void 0;
  109. if (typeof configuration._poll === "object") {
  110. newInfo.poll.timeout = configuration._poll.timeout ?? newInfo.poll.timeout;
  111. newInfo.poll.intervals = configuration._poll.intervals ?? newInfo.poll.intervals;
  112. }
  113. }
  114. return createExpect(newInfo, prefix, userMatchers);
  115. };
  116. return expectInstance;
  117. }
  118. let matcherCallContext;
  119. function setMatcherCallContext(context) {
  120. matcherCallContext = context;
  121. }
  122. function takeMatcherCallContext() {
  123. try {
  124. return matcherCallContext;
  125. } finally {
  126. matcherCallContext = void 0;
  127. }
  128. }
  129. const defaultExpectTimeout = 5e3;
  130. function wrapPlaywrightMatcherToPassNiceThis(matcher) {
  131. return function(...args) {
  132. const { isNot, promise, utils } = this;
  133. const context = takeMatcherCallContext();
  134. const timeout = context?.expectInfo.timeout ?? context?.testInfo?._projectInternal?.expect?.timeout ?? defaultExpectTimeout;
  135. const newThis = {
  136. isNot,
  137. promise,
  138. utils,
  139. timeout,
  140. _stepInfo: context?.step
  141. };
  142. newThis.equals = throwUnsupportedExpectMatcherError;
  143. return matcher.call(newThis, ...args);
  144. };
  145. }
  146. function throwUnsupportedExpectMatcherError() {
  147. throw new Error("It looks like you are using custom expect matchers that are not compatible with Playwright. See https://aka.ms/playwright/expect-compatibility");
  148. }
  149. import_expectBundle.expect.setState({ expand: false });
  150. const customAsyncMatchers = {
  151. toBeAttached: import_matchers.toBeAttached,
  152. toBeChecked: import_matchers.toBeChecked,
  153. toBeDisabled: import_matchers.toBeDisabled,
  154. toBeEditable: import_matchers.toBeEditable,
  155. toBeEmpty: import_matchers.toBeEmpty,
  156. toBeEnabled: import_matchers.toBeEnabled,
  157. toBeFocused: import_matchers.toBeFocused,
  158. toBeHidden: import_matchers.toBeHidden,
  159. toBeInViewport: import_matchers.toBeInViewport,
  160. toBeOK: import_matchers.toBeOK,
  161. toBeVisible: import_matchers.toBeVisible,
  162. toContainText: import_matchers.toContainText,
  163. toContainClass: import_matchers.toContainClass,
  164. toHaveAccessibleDescription: import_matchers.toHaveAccessibleDescription,
  165. toHaveAccessibleName: import_matchers.toHaveAccessibleName,
  166. toHaveAccessibleErrorMessage: import_matchers.toHaveAccessibleErrorMessage,
  167. toHaveAttribute: import_matchers.toHaveAttribute,
  168. toHaveClass: import_matchers.toHaveClass,
  169. toHaveCount: import_matchers.toHaveCount,
  170. toHaveCSS: import_matchers.toHaveCSS,
  171. toHaveId: import_matchers.toHaveId,
  172. toHaveJSProperty: import_matchers.toHaveJSProperty,
  173. toHaveRole: import_matchers.toHaveRole,
  174. toHaveText: import_matchers.toHaveText,
  175. toHaveTitle: import_matchers.toHaveTitle,
  176. toHaveURL: import_matchers.toHaveURL,
  177. toHaveValue: import_matchers.toHaveValue,
  178. toHaveValues: import_matchers.toHaveValues,
  179. toHaveScreenshot: import_toMatchSnapshot.toHaveScreenshot,
  180. toMatchAriaSnapshot: import_toMatchAriaSnapshot.toMatchAriaSnapshot,
  181. toPass: import_matchers.toPass
  182. };
  183. const customMatchers = {
  184. ...customAsyncMatchers,
  185. toMatchSnapshot: import_toMatchSnapshot.toMatchSnapshot
  186. };
  187. class ExpectMetaInfoProxyHandler {
  188. constructor(info, prefix) {
  189. this._info = { ...info };
  190. this._prefix = prefix;
  191. }
  192. get(target, matcherName, receiver) {
  193. let matcher = Reflect.get(target, matcherName, receiver);
  194. if (typeof matcherName !== "string")
  195. return matcher;
  196. let resolvedMatcherName = matcherName;
  197. for (let i = this._prefix.length; i > 0; i--) {
  198. const qualifiedName = qualifiedMatcherName(this._prefix.slice(0, i), matcherName);
  199. if (Reflect.has(target, qualifiedName)) {
  200. matcher = Reflect.get(target, qualifiedName, receiver);
  201. resolvedMatcherName = qualifiedName;
  202. break;
  203. }
  204. }
  205. if (matcher === void 0)
  206. throw new Error(`expect: Property '${matcherName}' not found.`);
  207. if (typeof matcher !== "function") {
  208. if (matcherName === "not")
  209. this._info.isNot = !this._info.isNot;
  210. return new Proxy(matcher, this);
  211. }
  212. if (this._info.poll) {
  213. if (customAsyncMatchers[matcherName] || matcherName === "resolves" || matcherName === "rejects")
  214. throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`);
  215. matcher = (...args) => pollMatcher(resolvedMatcherName, this._info, this._prefix, ...args);
  216. }
  217. return (...args) => {
  218. const testInfo = (0, import_globals.currentTestInfo)();
  219. setMatcherCallContext({ expectInfo: this._info, testInfo });
  220. if (!testInfo)
  221. return matcher.call(target, ...args);
  222. const customMessage = this._info.message || "";
  223. const argsSuffix = computeArgsSuffix(matcherName, args);
  224. const defaultTitle = `${this._info.poll ? "poll " : ""}${this._info.isSoft ? "soft " : ""}${this._info.isNot ? "not " : ""}${matcherName}${argsSuffix}`;
  225. const title = customMessage || `Expect ${(0, import_utils.escapeWithQuotes)(defaultTitle, '"')}`;
  226. const apiName = `expect${this._info.poll ? ".poll " : ""}${this._info.isSoft ? ".soft " : ""}${this._info.isNot ? ".not" : ""}.${matcherName}${argsSuffix}`;
  227. const stackFrames = (0, import_util.filteredStackTrace)((0, import_utils.captureRawStack)());
  228. const category = matcherName === "toPass" || this._info.poll ? "test.step" : "expect";
  229. const stepInfo = {
  230. category,
  231. apiName,
  232. title,
  233. params: args[0] ? { expected: args[0] } : void 0,
  234. infectParentStepsWithError: this._info.isSoft
  235. };
  236. const step = testInfo._addStep(stepInfo);
  237. const reportStepError = (isAsync, e) => {
  238. const jestError = (0, import_matcherHint.isJestError)(e) ? e : null;
  239. const expectError = jestError ? new import_matcherHint.ExpectError(jestError, customMessage, stackFrames) : void 0;
  240. if (jestError?.matcherResult.suggestedRebaseline) {
  241. step.complete({ suggestedRebaseline: jestError?.matcherResult.suggestedRebaseline });
  242. return;
  243. }
  244. const error = expectError ?? e;
  245. step.complete({ error });
  246. if (!isAsync || !expectError) {
  247. if (this._info.isSoft)
  248. testInfo._failWithError(error);
  249. else
  250. throw error;
  251. return;
  252. }
  253. return (async () => {
  254. const recoveryResult = await step.recoverFromStepError(expectError);
  255. if (recoveryResult.status === "recovered")
  256. return recoveryResult.value;
  257. if (this._info.isSoft)
  258. testInfo._failWithError(expectError);
  259. else
  260. throw expectError;
  261. })();
  262. };
  263. const finalizer = () => {
  264. step.complete({});
  265. };
  266. try {
  267. setMatcherCallContext({ expectInfo: this._info, testInfo, step: step.info });
  268. const callback = () => matcher.call(target, ...args);
  269. const result = (0, import_utils.currentZone)().with("stepZone", step).run(callback);
  270. if (result instanceof Promise)
  271. return result.then(finalizer).catch(reportStepError.bind(null, true));
  272. finalizer();
  273. return result;
  274. } catch (e) {
  275. void reportStepError(false, e);
  276. }
  277. };
  278. }
  279. }
  280. async function pollMatcher(qualifiedMatcherName2, info, prefix, ...args) {
  281. const testInfo = (0, import_globals.currentTestInfo)();
  282. const poll = info.poll;
  283. const timeout = poll.timeout ?? info.timeout ?? testInfo?._projectInternal?.expect?.timeout ?? defaultExpectTimeout;
  284. const { deadline, timeoutMessage } = testInfo ? testInfo._deadlineForMatcher(timeout) : import_testInfo.TestInfoImpl._defaultDeadlineForMatcher(timeout);
  285. const result = await (0, import_utils.pollAgainstDeadline)(async () => {
  286. if (testInfo && (0, import_globals.currentTestInfo)() !== testInfo)
  287. return { continuePolling: false, result: void 0 };
  288. const innerInfo = {
  289. ...info,
  290. isSoft: false,
  291. // soft is outside of poll, not inside
  292. poll: void 0
  293. };
  294. const value = await poll.generator();
  295. try {
  296. let matchers = createMatchers(value, innerInfo, prefix);
  297. if (info.isNot)
  298. matchers = matchers.not;
  299. matchers[qualifiedMatcherName2](...args);
  300. return { continuePolling: false, result: void 0 };
  301. } catch (error) {
  302. return { continuePolling: true, result: error };
  303. }
  304. }, deadline, poll.intervals ?? [100, 250, 500, 1e3]);
  305. if (result.timedOut) {
  306. const message = result.result ? [
  307. result.result.message,
  308. "",
  309. `Call Log:`,
  310. `- ${timeoutMessage}`
  311. ].join("\n") : timeoutMessage;
  312. throw new Error(message);
  313. }
  314. }
  315. function computeArgsSuffix(matcherName, args) {
  316. let value = "";
  317. if (matcherName === "toHaveScreenshot")
  318. value = (0, import_toMatchSnapshot.toHaveScreenshotStepTitle)(...args);
  319. return value ? `(${value})` : "";
  320. }
  321. const expect = createExpect({}, [], {}).extend(customMatchers);
  322. function mergeExpects(...expects) {
  323. let merged = expect;
  324. for (const e of expects) {
  325. const internals = e[userMatchersSymbol];
  326. if (!internals)
  327. continue;
  328. merged = merged.extend(internals);
  329. }
  330. return merged;
  331. }
  332. // Annotate the CommonJS export names for ESM import in node:
  333. 0 && (module.exports = {
  334. expect,
  335. mergeExpects,
  336. printReceivedStringContainExpectedResult,
  337. printReceivedStringContainExpectedSubstring
  338. });