system-harness.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /*
  2. Shared harness for the full-stack (real server) E2E specs.
  3. Each spec in specs-system/ is a standalone runnable Node script. When
  4. executed through run.js a shared server is already up and its address
  5. arrives via AROZ_BASE_URL; when a spec is run directly with no
  6. AROZ_BASE_URL, the harness boots its own disposable server so
  7. `node specs-system/010-auth.js` still works on its own.
  8. Env:
  9. AROZ_BASE_URL base URL of an already-running test instance
  10. AROZ_ADMIN_USER admin username of that instance (default "admin")
  11. AROZ_ADMIN_PASS admin password of that instance
  12. PW_CHROMIUM_PATH optional explicit Chromium binary
  13. */
  14. "use strict";
  15. const { chromium } = require("playwright");
  16. const arozosServer = require("./arozos-server");
  17. function ok(msg) { console.log(" PASS: " + msg); }
  18. function fail(msg) {
  19. console.error(" FAIL: " + msg);
  20. process.exit(1);
  21. }
  22. function launch() {
  23. return chromium.launch({
  24. executablePath: process.env.PW_CHROMIUM_PATH || undefined,
  25. args: ["--autoplay-policy=no-user-gesture-required"]
  26. });
  27. }
  28. async function newPage(browser) {
  29. const context = await browser.newContext({ viewport: { width: 1366, height: 900 } });
  30. const page = await context.newPage();
  31. page.on("pageerror", function (e) { console.log(" [pageerror] " + e.message); });
  32. return page;
  33. }
  34. // Log in through the real login form UI. Resolves once the browser has
  35. // been redirected away from login.html.
  36. async function loginViaForm(page, baseURL, username, password) {
  37. await page.goto(baseURL + "/login.html", { waitUntil: "domcontentloaded" });
  38. await page.fill("#username", username);
  39. await page.fill("#magic", password);
  40. await Promise.all([
  41. page.waitForURL(function (url) { return url.pathname.indexOf("login.html") === -1; }, { timeout: 15000 }),
  42. page.click("#loginbtn")
  43. ]);
  44. }
  45. // Log in via the auth API using the page's cookie jar (fast path for
  46. // specs that are not about the login UI itself).
  47. async function loginViaAPI(page, baseURL, username, password) {
  48. const res = await page.request.post(baseURL + "/system/auth/login", {
  49. form: { username: username, password: password, rmbme: "false" }
  50. });
  51. const body = (await res.text()).trim();
  52. const authed = (await (await page.request.get(baseURL + "/system/auth/checkLogin")).text()).trim();
  53. if (authed !== "true") {
  54. throw new Error("API login failed for " + username + ": " + body);
  55. }
  56. }
  57. async function logout(page, baseURL) {
  58. await page.request.get(baseURL + "/system/auth/logout");
  59. }
  60. async function isLoggedIn(page, baseURL) {
  61. const res = await page.request.get(baseURL + "/system/auth/checkLogin");
  62. return (await res.text()).trim() === "true";
  63. }
  64. // GET a JSON endpoint with the page's session cookies.
  65. async function getJSON(page, url) {
  66. const res = await page.request.get(url);
  67. const text = await res.text();
  68. try {
  69. return JSON.parse(text);
  70. } catch (e) {
  71. throw new Error("Expected JSON from " + url + " but got: " + text.slice(0, 200));
  72. }
  73. }
  74. // POST form parameters, returning the raw response text.
  75. async function postForm(page, url, form) {
  76. const res = await page.request.post(url, { form: form });
  77. return (await res.text()).trim();
  78. }
  79. // Fetch a fresh CSRF token for endpoints that require one (fileOpr, newItem).
  80. async function csrfToken(page, baseURL) {
  81. const token = await getJSON(page, baseURL + "/system/csrf/new");
  82. if (typeof token !== "string" || !token.length) {
  83. throw new Error("Could not obtain CSRF token");
  84. }
  85. return token;
  86. }
  87. /*
  88. Wrap a spec body. Boots a private server when AROZ_BASE_URL is not
  89. provided, launches the browser, and reports the pass/fail banner.
  90. body receives ({ browser, baseURL, admin }).
  91. */
  92. function run(name, body) {
  93. (async function () {
  94. let server = null;
  95. let baseURL = process.env.AROZ_BASE_URL;
  96. let admin = {
  97. username: process.env.AROZ_ADMIN_USER || arozosServer.ADMIN_USER,
  98. password: process.env.AROZ_ADMIN_PASS || arozosServer.ADMIN_PASS
  99. };
  100. if (!baseURL) {
  101. console.log(" AROZ_BASE_URL not set - booting a private ArozOS test instance...");
  102. server = await arozosServer.start({});
  103. baseURL = server.baseURL;
  104. admin = server.admin;
  105. }
  106. const browser = await launch();
  107. try {
  108. await body({ browser: browser, baseURL: baseURL, admin: admin });
  109. console.log("ALL " + name + " TESTS PASSED");
  110. } finally {
  111. await browser.close();
  112. if (server) { await server.stop(); }
  113. }
  114. })().catch(function (e) {
  115. console.error(e);
  116. process.exit(1);
  117. });
  118. }
  119. module.exports = {
  120. ok,
  121. fail,
  122. launch,
  123. newPage,
  124. loginViaForm,
  125. loginViaAPI,
  126. logout,
  127. isLoggedIn,
  128. getJSON,
  129. postForm,
  130. csrfToken,
  131. run
  132. };