Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

485 Zeilen
13 KiB

  1. import "cypress-file-upload";
  2. import "@testing-library/cypress/add-commands";
  3. import "@4tw/cypress-drag-drop";
  4. import "cypress-real-events/support";
  5. // ***********************************************
  6. // This example commands.js shows you how to
  7. // create various custom commands and overwrite
  8. // existing commands.
  9. //
  10. // For more comprehensive examples of custom
  11. // commands please read more here:
  12. // https://on.cypress.io/custom-commands
  13. // ***********************************************
  14. //
  15. //
  16. // -- This is a parent command --
  17. // Cypress.Commands.add("login", (email, password) => { ... });
  18. //
  19. //
  20. // -- This is a child command --
  21. // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... });
  22. //
  23. //
  24. // -- This is a dual command --
  25. // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... });
  26. //
  27. //
  28. // -- This is will overwrite an existing command --
  29. // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... });
  30. Cypress.Commands.add("login", (email, password) => {
  31. if (!email) {
  32. email = "Administrator";
  33. }
  34. if (!password) {
  35. password = Cypress.env("adminPassword");
  36. }
  37. cy.request({
  38. url: "/api/method/login",
  39. method: "POST",
  40. body: {
  41. usr: email,
  42. pwd: password,
  43. },
  44. });
  45. });
  46. Cypress.Commands.add("call", (method, args) => {
  47. return cy
  48. .window()
  49. .its("frappe.csrf_token")
  50. .then((csrf_token) => {
  51. return cy
  52. .request({
  53. url: `/api/method/${method}`,
  54. method: "POST",
  55. body: args,
  56. headers: {
  57. Accept: "application/json",
  58. "Content-Type": "application/json",
  59. "X-Frappe-CSRF-Token": csrf_token,
  60. },
  61. })
  62. .then((res) => {
  63. expect(res.status).eq(200);
  64. return res.body;
  65. });
  66. });
  67. });
  68. Cypress.Commands.add("get_list", (doctype, fields = [], filters = []) => {
  69. filters = JSON.stringify(filters);
  70. fields = JSON.stringify(fields);
  71. let url = `/api/resource/${doctype}?fields=${fields}&filters=${filters}`;
  72. return cy
  73. .window()
  74. .its("frappe.csrf_token")
  75. .then((csrf_token) => {
  76. return cy
  77. .request({
  78. method: "GET",
  79. url,
  80. headers: {
  81. Accept: "application/json",
  82. "X-Frappe-CSRF-Token": csrf_token,
  83. },
  84. })
  85. .then((res) => {
  86. expect(res.status).eq(200);
  87. return res.body;
  88. });
  89. });
  90. });
  91. Cypress.Commands.add("get_doc", (doctype, name) => {
  92. return cy
  93. .window()
  94. .its("frappe.csrf_token")
  95. .then((csrf_token) => {
  96. return cy
  97. .request({
  98. method: "GET",
  99. url: `/api/resource/${doctype}/${name}`,
  100. headers: {
  101. Accept: "application/json",
  102. "X-Frappe-CSRF-Token": csrf_token,
  103. },
  104. })
  105. .then((res) => {
  106. expect(res.status).eq(200);
  107. return res.body;
  108. });
  109. });
  110. });
  111. Cypress.Commands.add("remove_doc", (doctype, name) => {
  112. return cy
  113. .window()
  114. .its("frappe.csrf_token")
  115. .then((csrf_token) => {
  116. return cy
  117. .request({
  118. method: "DELETE",
  119. url: `/api/resource/${doctype}/${name}`,
  120. headers: {
  121. Accept: "application/json",
  122. "X-Frappe-CSRF-Token": csrf_token,
  123. },
  124. })
  125. .then((res) => {
  126. expect(res.status).eq(202);
  127. return res.body;
  128. });
  129. });
  130. });
  131. Cypress.Commands.add("create_records", (doc) => {
  132. return cy
  133. .call("frappe.tests.ui_test_helpers.create_if_not_exists", { doc: JSON.stringify(doc) })
  134. .then((r) => r.message);
  135. });
  136. Cypress.Commands.add("set_value", (doctype, name, obj) => {
  137. return cy.call("frappe.client.set_value", {
  138. doctype,
  139. name,
  140. fieldname: obj,
  141. });
  142. });
  143. Cypress.Commands.add("fill_field", (fieldname, value, fieldtype = "Data") => {
  144. cy.get_field(fieldname, fieldtype).as("input");
  145. if (["Date", "Time", "Datetime"].includes(fieldtype)) {
  146. cy.get("@input").click().wait(200);
  147. cy.get(".datepickers-container .datepicker.active").should("exist");
  148. }
  149. if (fieldtype === "Time") {
  150. cy.get("@input").clear().wait(200);
  151. }
  152. if (fieldtype === "Select") {
  153. cy.get("@input").select(value);
  154. } else {
  155. cy.get("@input").type(value, {
  156. waitForAnimations: false,
  157. parseSpecialCharSequences: false,
  158. force: true,
  159. delay: 100,
  160. });
  161. }
  162. return cy.get("@input");
  163. });
  164. Cypress.Commands.add("get_field", (fieldname, fieldtype = "Data") => {
  165. let field_element = fieldtype === "Select" ? "select" : "input";
  166. let selector = `[data-fieldname="${fieldname}"] ${field_element}:visible`;
  167. if (fieldtype === "Text Editor") {
  168. selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]:visible`;
  169. }
  170. if (fieldtype === "Code") {
  171. selector = `[data-fieldname="${fieldname}"] .ace_text-input`;
  172. }
  173. if (fieldtype === "Markdown Editor") {
  174. selector = `[data-fieldname="${fieldname}"] .ace-editor-target`;
  175. }
  176. return cy.get(selector).first();
  177. });
  178. Cypress.Commands.add(
  179. "fill_table_field",
  180. (tablefieldname, row_idx, fieldname, value, fieldtype = "Data") => {
  181. cy.get_table_field(tablefieldname, row_idx, fieldname, fieldtype).as("input");
  182. if (["Date", "Time", "Datetime"].includes(fieldtype)) {
  183. cy.get("@input").click().wait(200);
  184. cy.get(".datepickers-container .datepicker.active").should("exist");
  185. }
  186. if (fieldtype === "Time") {
  187. cy.get("@input").clear().wait(200);
  188. }
  189. if (fieldtype === "Select") {
  190. cy.get("@input").select(value);
  191. } else {
  192. cy.get("@input").type(value, { waitForAnimations: false, force: true });
  193. }
  194. return cy.get("@input");
  195. }
  196. );
  197. Cypress.Commands.add(
  198. "get_table_field",
  199. (tablefieldname, row_idx, fieldname, fieldtype = "Data") => {
  200. let selector = `.frappe-control[data-fieldname="${tablefieldname}"]`;
  201. selector += ` [data-idx="${row_idx}"]`;
  202. if (fieldtype === "Text Editor") {
  203. selector += ` [data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`;
  204. } else if (fieldtype === "Code") {
  205. selector += ` [data-fieldname="${fieldname}"] .ace_text-input`;
  206. } else {
  207. selector += ` [data-fieldname="${fieldname}"]`;
  208. return cy.get(selector).find(".form-control:visible, .static-area:visible").first();
  209. }
  210. return cy.get(selector);
  211. }
  212. );
  213. Cypress.Commands.add("awesomebar", (text) => {
  214. cy.get("#navbar-search").type(`${text}{downarrow}{enter}`, { delay: 700 });
  215. });
  216. Cypress.Commands.add("new_form", (doctype) => {
  217. let dt_in_route = doctype.toLowerCase().replace(/ /g, "-");
  218. cy.visit(`/app/${dt_in_route}/new`);
  219. cy.get("body").should("have.attr", "data-route", `Form/${doctype}/new-${dt_in_route}-1`);
  220. cy.get("body").should("have.attr", "data-ajax-state", "complete");
  221. });
  222. Cypress.Commands.add("go_to_list", (doctype) => {
  223. let dt_in_route = doctype.toLowerCase().replace(/ /g, "-");
  224. cy.visit(`/app/${dt_in_route}`);
  225. });
  226. Cypress.Commands.add("clear_cache", () => {
  227. cy.window()
  228. .its("frappe")
  229. .then((frappe) => {
  230. frappe.ui.toolbar.clear_cache();
  231. });
  232. });
  233. Cypress.Commands.add("dialog", (opts) => {
  234. return cy
  235. .window({ log: false })
  236. .its("frappe", { log: false })
  237. .then((frappe) => {
  238. Cypress.log({
  239. name: "dialog",
  240. displayName: "dialog",
  241. message: "frappe.ui.Dialog",
  242. consoleProps: () => {
  243. return {
  244. options: opts,
  245. dialog: d,
  246. };
  247. },
  248. });
  249. var d = new frappe.ui.Dialog(opts);
  250. d.show();
  251. return d;
  252. });
  253. });
  254. Cypress.Commands.add("get_open_dialog", () => {
  255. return cy.get(".modal:visible").last();
  256. });
  257. Cypress.Commands.add("save", () => {
  258. cy.intercept("/api/method/frappe.desk.form.save.savedocs").as("save_call");
  259. cy.get(`button[data-label="Save"]:visible`).click({ scrollBehavior: false, force: true });
  260. cy.wait("@save_call");
  261. });
  262. Cypress.Commands.add("hide_dialog", () => {
  263. cy.wait(500);
  264. cy.get_open_dialog().focus().find(".btn-modal-close").click();
  265. cy.get(".modal:visible").should("not.exist");
  266. });
  267. Cypress.Commands.add("clear_dialogs", () => {
  268. cy.window().then((win) => {
  269. win.$(".modal, .modal-backdrop").remove();
  270. });
  271. cy.get(".modal").should("not.exist");
  272. });
  273. Cypress.Commands.add("clear_datepickers", () => {
  274. cy.window().then((win) => {
  275. win.$(".datepicker").remove();
  276. });
  277. cy.get(".datepicker").should("not.exist");
  278. });
  279. Cypress.Commands.add("insert_doc", (doctype, args, ignore_duplicate) => {
  280. if (!args.doctype) {
  281. args.doctype = doctype;
  282. }
  283. return cy
  284. .window()
  285. .its("frappe.csrf_token")
  286. .then((csrf_token) => {
  287. return cy
  288. .request({
  289. method: "POST",
  290. url: `/api/resource/${doctype}`,
  291. body: args,
  292. headers: {
  293. Accept: "application/json",
  294. "Content-Type": "application/json",
  295. "X-Frappe-CSRF-Token": csrf_token,
  296. },
  297. failOnStatusCode: !ignore_duplicate,
  298. })
  299. .then((res) => {
  300. let status_codes = [200];
  301. if (ignore_duplicate) {
  302. status_codes.push(409);
  303. }
  304. let message = null;
  305. if (ignore_duplicate && !status_codes.includes(res.status)) {
  306. message = `Document insert failed, response: ${JSON.stringify(
  307. res,
  308. null,
  309. "\t"
  310. )}`;
  311. }
  312. expect(res.status).to.be.oneOf(status_codes, message);
  313. return res.body.data;
  314. });
  315. });
  316. });
  317. Cypress.Commands.add("update_doc", (doctype, docname, args) => {
  318. return cy
  319. .window()
  320. .its("frappe.csrf_token")
  321. .then((csrf_token) => {
  322. return cy
  323. .request({
  324. method: "PUT",
  325. url: `/api/resource/${doctype}/${docname}`,
  326. body: args,
  327. headers: {
  328. Accept: "application/json",
  329. "Content-Type": "application/json",
  330. "X-Frappe-CSRF-Token": csrf_token,
  331. },
  332. })
  333. .then((res) => {
  334. expect(res.status).to.eq(200);
  335. return res.body.data;
  336. });
  337. });
  338. });
  339. Cypress.Commands.add("open_list_filter", () => {
  340. cy.get(".filter-section .filter-button").click();
  341. cy.wait(300);
  342. cy.get(".filter-popover").should("exist");
  343. });
  344. Cypress.Commands.add("click_custom_action_button", (name) => {
  345. cy.get(`.custom-actions [data-label="${encodeURIComponent(name)}"]`).click();
  346. });
  347. Cypress.Commands.add("click_action_button", (name) => {
  348. cy.findByRole("button", { name: "Actions" }).click();
  349. cy.get(`.actions-btn-group [data-label="${encodeURIComponent(name)}"]`).click();
  350. });
  351. Cypress.Commands.add("click_menu_button", (name) => {
  352. cy.get(".standard-actions .menu-btn-group > .btn").click();
  353. cy.get(`.menu-btn-group [data-label="${encodeURIComponent(name)}"]`).click();
  354. });
  355. Cypress.Commands.add("clear_filters", () => {
  356. let has_filter = false;
  357. cy.intercept({
  358. method: "POST",
  359. url: "api/method/frappe.model.utils.user_settings.save",
  360. }).as("filter-saved");
  361. cy.get(".filter-section .filter-button").click({ force: true });
  362. cy.wait(300);
  363. cy.get(".filter-popover").should("exist");
  364. cy.get(".filter-popover").then((popover) => {
  365. if (popover.find("input.input-with-feedback")[0].value != "") {
  366. has_filter = true;
  367. }
  368. });
  369. cy.get(".filter-popover").find(".clear-filters").click();
  370. cy.get(".filter-section .filter-button").click();
  371. cy.window()
  372. .its("cur_list")
  373. .then((cur_list) => {
  374. cur_list && cur_list.filter_area && cur_list.filter_area.clear();
  375. has_filter && cy.wait("@filter-saved");
  376. });
  377. });
  378. Cypress.Commands.add("click_modal_primary_button", (btn_name) => {
  379. cy.wait(400);
  380. cy.get(".modal-footer > .standard-actions > .btn-primary")
  381. .contains(btn_name)
  382. .click({ force: true });
  383. });
  384. Cypress.Commands.add("click_sidebar_button", (btn_name) => {
  385. cy.get(".list-group-by-fields .list-link > a").contains(btn_name).click({ force: true });
  386. });
  387. Cypress.Commands.add("click_listview_row_item", (row_no) => {
  388. cy.get(".list-row > .level-left > .list-subject > .level-item > .ellipsis")
  389. .eq(row_no)
  390. .click({ force: true });
  391. });
  392. Cypress.Commands.add("click_listview_row_item_with_text", (text) => {
  393. cy.get(".list-row > .level-left > .list-subject > .level-item > .ellipsis")
  394. .contains(text)
  395. .first()
  396. .click({ force: true });
  397. });
  398. Cypress.Commands.add("click_filter_button", () => {
  399. cy.get(".filter-selector > .btn").click();
  400. });
  401. Cypress.Commands.add("click_listview_primary_button", (btn_name) => {
  402. cy.get(".primary-action").contains(btn_name).click({ force: true });
  403. });
  404. Cypress.Commands.add("click_doc_primary_button", (btn_name) => {
  405. cy.get(".primary-action").contains(btn_name).click({ force: true });
  406. });
  407. Cypress.Commands.add("click_timeline_action_btn", (btn_name) => {
  408. cy.get(".timeline-message-box .actions .action-btn").contains(btn_name).click();
  409. });
  410. Cypress.Commands.add("select_listview_row_checkbox", (row_no) => {
  411. cy.get(".frappe-list .select-like > .list-row-checkbox").eq(row_no).click();
  412. });
  413. Cypress.Commands.add("click_form_section", (section_name) => {
  414. cy.get(".section-head").contains(section_name).click();
  415. });
  416. const compare_document = (expected, actual) => {
  417. for (const prop in expected) {
  418. if (expected[prop] instanceof Array) {
  419. // recursively compare child documents.
  420. expected[prop].forEach((item, idx) => {
  421. compare_document(item, actual[prop][idx]);
  422. });
  423. } else {
  424. assert.equal(expected[prop], actual[prop], `${prop} should be equal.`);
  425. }
  426. }
  427. };
  428. Cypress.Commands.add("compare_document", (expected_document) => {
  429. cy.window()
  430. .its("cur_frm")
  431. .then((frm) => {
  432. // Don't remove this, cypress can't magically wait for events it has no control over.
  433. cy.wait(1000);
  434. compare_document(expected_document, frm.doc);
  435. });
  436. });