ui5-test-runner 6.0.0-beta.5 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -29
- package/dist/Command.js +52 -0
- package/dist/Npm.js +154 -74
- package/dist/browsers/RecoverableBrowserError.js +6 -0
- package/dist/browsers/chromeArguments.js +5 -0
- package/dist/browsers/chromeOptions.js +29 -0
- package/dist/browsers/consoleMessage.js +23 -0
- package/dist/browsers/factory.js +168 -3
- package/dist/browsers/networkResponse.js +15 -0
- package/dist/browsers/playwright.js +104 -0
- package/dist/browsers/puppeteer.js +54 -88
- package/dist/browsers/seleniumWebdriver.js +124 -0
- package/dist/browsers/webdriverio.js +85 -0
- package/dist/cli.js +18 -12
- package/dist/configuration/CommandLine.js +46 -26
- package/dist/configuration/ConfigurationValidator.js +132 -18
- package/dist/configuration/OptionValidationError.js +50 -2
- package/dist/configuration/derivations.js +8 -0
- package/dist/configuration/options.js +380 -23
- package/dist/configuration/validations.js +135 -0
- package/dist/configuration/validators/boolean.js +1 -1
- package/dist/configuration/validators/enumeration.js +7 -0
- package/dist/configuration/validators/fsEntry.js +12 -12
- package/dist/configuration/validators/index.js +8 -2
- package/dist/configuration/validators/integer.js +7 -1
- package/dist/configuration/validators/json.js +23 -0
- package/dist/configuration/validators/libraryMapping.js +28 -0
- package/dist/configuration/validators/percent.js +2 -2
- package/dist/configuration/validators/regexp.js +2 -2
- package/dist/configuration/validators/secret.js +8 -0
- package/dist/configuration/validators/string.js +1 -1
- package/dist/configuration/validators/timeout.js +2 -2
- package/dist/configuration/validators/url.js +3 -3
- package/dist/end.js +58 -0
- package/dist/if.js +22 -0
- package/dist/modes/Modes.js +2 -1
- package/dist/modes/batch/BatchItem.js +1 -0
- package/dist/modes/batch/batchTask.js +159 -0
- package/dist/modes/batch/index.js +98 -0
- package/dist/modes/batch/output.js +22 -0
- package/dist/modes/batch/report.js +45 -0
- package/dist/modes/batch/resolve.js +105 -0
- package/dist/modes/dumpConfig.js +6 -0
- package/dist/modes/execute.js +6 -5
- package/dist/modes/help.js +86 -2
- package/dist/modes/log/LogReader.js +3 -2
- package/dist/modes/log/LogStorage.js +31 -27
- package/dist/modes/log/index.js +38 -6
- package/dist/modes/log/{REserve.js → reserve.js} +25 -2
- package/dist/modes/mcp/IDocument.js +2 -0
- package/dist/modes/mcp/frontmatter.js +142 -0
- package/dist/modes/mcp/index.js +49 -0
- package/dist/modes/mcp/knowledgeBase.js +104 -0
- package/dist/modes/mcp/remoteKnowledgeBase.js +79 -0
- package/dist/modes/mcp/reserve.js +175 -0
- package/dist/modes/mcp/tools/getTopic.js +46 -0
- package/dist/modes/mcp/tools/index.js +6 -0
- package/dist/modes/mcp/tools/listTopics.js +14 -0
- package/dist/modes/test/PageContext.js +1 -0
- package/dist/modes/test/Server.js +99 -0
- package/dist/modes/test/browser.js +31 -17
- package/dist/modes/test/browserConfig.js +17 -0
- package/dist/modes/test/coverage/collect.js +38 -0
- package/dist/modes/test/coverage/index.js +3 -0
- package/dist/modes/test/coverage/instrument.js +70 -0
- package/dist/modes/test/coverage/nyc.js +15 -0
- package/dist/modes/test/coverage/report.js +71 -0
- package/dist/modes/test/coverage/settings.js +53 -0
- package/dist/modes/test/index.js +94 -34
- package/dist/modes/test/pageTask.js +293 -98
- package/dist/modes/test/report.js +20 -3
- package/dist/modes/test/reserve.js +76 -0
- package/dist/modes/test/screenshot.js +49 -0
- package/dist/platform/Crypto.js +9 -0
- package/dist/platform/Exit.js +33 -24
- package/dist/platform/FileSystem.js +6 -2
- package/dist/platform/Host.js +9 -1
- package/dist/platform/Http.js +54 -29
- package/dist/platform/Module.js +2 -1
- package/dist/platform/Path.js +5 -1
- package/dist/platform/Process.js +67 -13
- package/dist/platform/Terminal.js +12 -8
- package/dist/platform/Thread.js +1 -1
- package/dist/platform/assert.js +13 -12
- package/dist/platform/environment.js +9 -18
- package/dist/platform/index.js +1 -0
- package/dist/platform/logger/CompressionContext.js +117 -0
- package/dist/platform/logger/allCompressed.js +8 -7
- package/dist/platform/logger/compress.js +29 -129
- package/dist/platform/logger/output/BaseLoggerOutput.js +49 -31
- package/dist/platform/logger/output/InteractiveLoggerOutput.js +1 -1
- package/dist/platform/logger/output/LlmLoggerOutput.js +29 -0
- package/dist/platform/logger/output/factory.js +5 -1
- package/dist/platform/logger/output.js +24 -6
- package/dist/platform/logger/toInternalLogAttributes.js +10 -11
- package/dist/platform/logger/traceFileName.js +1 -0
- package/dist/platform/logger.js +52 -21
- package/dist/platform/mock.js +110 -19
- package/dist/platform/version.js +1 -2
- package/dist/platform/workerBootstrap.js +1 -1
- package/dist/reports/initReportBuilder.js +18 -0
- package/dist/reports/junit.js +64 -0
- package/dist/reports/saveReport.js +10 -0
- package/dist/reports/textReport.js +92 -0
- package/dist/reserveLogger.js +22 -0
- package/dist/sendToParentProcess.js +6 -0
- package/dist/start.js +79 -0
- package/dist/types/ISecretValue.js +1 -0
- package/dist/types/SecretValue.js +15 -0
- package/dist/ui/agent.js +4 -2
- package/dist/ui/html-report.js +2 -2
- package/dist/ui/lib.js +1 -1
- package/dist/ui/log-viewer.js +1 -1
- package/dist/utils/node/Folder.js +2 -4
- package/dist/utils/node/FramedStreamReader.js +4 -5
- package/dist/utils/node/FramedStreamWriter.js +6 -5
- package/dist/utils/node/anonymize.js +11 -0
- package/dist/utils/node/timeout.js +11 -0
- package/dist/utils/shared/ProgressBar.js +1 -2
- package/dist/utils/shared/TestReportBuilder.js +11 -7
- package/dist/utils/shared/host.js +11 -0
- package/dist/utils/shared/object.js +12 -3
- package/dist/utils/shared/schema.js +20 -0
- package/dist/utils/shared/string.js +1 -0
- package/dist/utils/shared/toIError.js +3 -7
- package/dist/utils/shared/version.js +52 -0
- package/package.json +2 -78
- package/dist/configuration/validators/browser.js +0 -11
- package/dist/modes/test/REserve.js +0 -31
- package/dist/modes/test/server.js +0 -109
package/dist/ui/log-viewer.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
!function(){try{if("undefined"!=typeof document){var e=document.createElement("style");e.appendChild(document.createTextNode('body,#app{height:100vh;font-family:var(--sapFontFamily,"72", Arial, sans-serif);background:var(--sapBackgroundColor,#f7f7f7);flex-direction:column;margin:0;display:flex;overflow:hidden}#header{background:var(--sapShellBar_Background,#354a5e);color:var(--sapShellBar_TextColor,#fff);flex:none;align-items:center;gap:1rem;padding:.5rem 1rem;display:flex}#header h1{color:inherit;flex:1;margin:0;font-size:1rem;font-weight:700}#toolbar{background:var(--sapGroup_ContentBackground,#fff);border-bottom:1px solid var(--sapGroup_TitleBorderColor,#d9d9d9);flex-wrap:wrap;flex:none;align-items:center;gap:.75rem;padding:.5rem 1rem;display:flex}#toolbar ui5-message-strip{flex:0 0 100%;margin-top:.25rem}#logTableWrapper{flex:1;overflow-y:auto}#logTable{border-collapse:collapse;width:100%;font-size:.875rem}#logTable thead th{background:var(--sapList_HeaderBackground,#f7f7f7);text-align:left;border-bottom:1px solid var(--sapList_BorderColor,#d9d9d9);white-space:nowrap;padding:.4rem .5rem;font-weight:700;position:sticky;top:0}#logTable thead th.col-clickable{cursor:pointer;-webkit-user-select:none;user-select:none}#logTable thead th.col-clickable:hover{background:var(--sapList_Hover_Background,#f1f1f1);text-decoration:underline}#logTable tbody tr{cursor:pointer;border-bottom:1px solid var(--sapList_BorderColor,#d9d9d9)}#logTable tbody tr:hover{background:var(--sapList_Hover_Background,#f1f1f1)}#logTable tbody td{vertical-align:top;text-overflow:ellipsis;white-space:nowrap;max-width:400px;padding:.25rem .5rem;overflow:hidden}#logTable tbody td.col-message{max-width:600px}.log-details-row{align-items:baseline;gap:.5rem;min-width:400px;padding:.2rem 0;display:flex}.log-details-label{color:var(--sapContent_LabelColor,#6a6d70);flex-shrink:0;min-width:6rem;font-size:.8rem}.log-details-value{word-break:break-all;flex:1}.log-details-actions{flex-shrink:0;gap:.25rem;display:flex}.log-details-error{white-space:pre-wrap;background:var(--sapField_ReadOnly_Background,#f2f2f2);border-radius:.25rem;flex:1;max-height:200px;padding:.5rem;font-family:monospace;font-size:.75rem;overflow-y:auto}.log-details-section-label{color:var(--sapContent_LabelColor,#6a6d70);margin-top:.5rem;margin-bottom:.25rem;font-size:.8rem;font-weight:700}.column-filter-row{align-items:center;padding:.2rem 0;display:flex}\n/*$vite$:1*/')),document.head.appendChild(e)}}catch(o){console.error("vite-plugin-css-injected-by-js",o)}}();
|
|
2
|
-
!function(){var e=class{_state={};get state(){return this._state}_settings={};get settings(){return this._settings}_updateCb=()=>{throw new Error("UI not connected")};_update({...e}){for(const t of Object.keys(e))e[t]===this._state[t]&&delete e[t];return Object.assign(this._state,e),console.log("🎮⏩",e),Object.keys(e).length>0&&this._updateCb({...e}),e}_onConnect(){}connect(e){this._updateCb=e,this._onConnect(),console.log("🎮🔛",{initialState:{...this._state},settings:this._settings})}interaction(e){const{action:t,...n}=e,r=this._update(n);console.log("🎮⏪",{event:e,action:t,stateDiff:r}),(Object.keys(r).length>0||void 0!==t)&&this._onInteraction(r,t)}},t=3e5,n=[{label:"5 minutes",key:t},{label:"15 minutes",key:9e5},{label:"30 minutes",key:18e5},{label:"1 hour",key:36e5},{label:"3 hours",key:108e5}],r=[{label:"5 seconds",key:5e3},{label:"10 seconds",key:1e4},{label:"30 seconds",key:3e4},{label:"60 seconds",key:6e4}];function o(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}function a(e){return e.toString().padStart(2,"0")}function s(e){const t=new Date(e);return`${a(t.getMonth()+1)}/${a(t.getDate())}/${t.getFullYear()}, ${a(t.getHours())}:${a(t.getMinutes())}:${a(t.getSeconds())}`}function i(e,t){const n="relative"===e.timerangeType,r=n?function(e,t){const n=t.relativeTimerange.map(t=>`<ui5-option value="${t.key}"${e.relativeTimerange===t.key?" selected":""}>${t.label}</ui5-option>`).join(""),r=t.autorefresh.map(t=>`<ui5-option value="${t.key}"${e.autorefreshInterval===t.key?" selected":""}>${t.label}</ui5-option>`).join("");return`<ui5-select id="relativeTimerangeSelect">${n}</ui5-select>\n <span>Auto Refresh:</span>\n <ui5-select id="autorefreshSelect">\n <ui5-option value="none"${e.autorefresh?"":" selected"}>None</ui5-option>\n ${r}\n </ui5-select>`}(e,t):function(e){return`<ui5-datetime-picker id="absoluteFromPicker" value="${s(e.absoluteTimerangeFrom)}" placeholder="From"></ui5-datetime-picker>\n <ui5-datetime-picker id="absoluteToPicker" value="${s(e.absoluteTimerangeTo)}" placeholder="To"></ui5-datetime-picker>`}(e),a=e.errorMessage?`<ui5-message-strip id="errorStrip" design="Negative" hide-close-button>${o(e.errorMessage)}</ui5-message-strip>`:"";return`<ui5-input id="filterInput" value="${o(e.filter)}" placeholder="Filter expression..." style="flex:1;min-width:200px;"></ui5-input>\n <ui5-select id="timerangeTypeSelect">\n <ui5-option value="relative"${n?" selected":""}>Relative</ui5-option>\n <ui5-option value="absolute"${n?"":" selected"}>Absolute</ui5-option>\n </ui5-select>\n ${r}\n <ui5-button id="refreshNowBtn">Refresh now</ui5-button>\n ${a}`}var l={0:"🔍",1:"💬",2:"⚠️",3:"❌",4:"💣"},c={0:"debug",1:"info",2:"warn",3:"error",4:"fatal"},u=["B","KB","MB","GB"];function d(e){return l[e]??"?"}function p(e){return e?new Date(e).toLocaleString():"—"}function g(e){let t=e,n=0;for(;t>=1024&&n<u.length-1;)t/=1024,n++;return`${0===n?t.toString():t.toFixed(1)} ${u[n]}`}function m(e){const t='<thead><tr>\n <th data-col="timestamp" class="col-clickable">Timestamp (local)</th>\n <th data-col="level" class="col-clickable">Level</th>\n <th data-col="source" class="col-clickable">Source</th>\n <th data-col="processId" class="col-clickable">PID</th>\n <th data-col="threadId" class="col-clickable">TID</th>\n <th data-col="pageId" class="col-clickable">Page ID</th>\n <th class="col-message">Message</th>\n</tr></thead>';return 0===e.length?`${t}<tbody><tr><td colspan="7" style="text-align:center;padding:1rem;color:var(--sapContent_LabelColor);">No log entries</td></tr></tbody>`:`${t}<tbody>${e.map((e,t)=>{const n=e.message.length>200?`${e.message.slice(0,200)}…`:e.message;return`<tr data-index="${t}">\n <td>${p(e.timestamp)}</td>\n <td>${d(e.level)}</td>\n <td>${e.source}</td>\n <td>${e.processId}</td>\n <td>${e.threadId}</td>\n <td>${e.pageId??""}</td>\n <td class="col-message">${r=n,r.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}</td>\n</tr>`;var r}).join("")}</tbody>`}function v(e,t){const n=JSON.stringify(t);return`<span class="log-details-actions">\n <ui5-button class="filter-add-btn" design="Transparent" data-field="${e}" data-value='${n}' data-op="===">➕</ui5-button>\n <ui5-button class="filter-remove-btn" design="Transparent" data-field="${e}" data-value='${n}' data-op="!==">➖</ui5-button>\n </span>`}function f(e,t,n=""){return`<div class="log-details-row">\n <span class="log-details-label">${e}</span>\n <span class="log-details-value">${t}</span>\n ${n}\n</div>`}function h(e,t="data"){return Object.entries(e).map(([e,n])=>{const r=`${t}.${e}`;return Array.isArray(n)?n.map((e,t)=>{const n=`${r}[${t}]`;return"object"==typeof e&&null!==e?h(e,n):f(n,b(String(e)),v(n,e))}).join(""):"object"==typeof n&&null!==n?h(n,r):f(r,b(String(n)),v(r,n))}).join("")}function b(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}function y(e){const t=d(e.level),n=(r=e.level,c[r]??"unknown");var r;let o=f("timestamp",p(e.timestamp));var a;return o+=f("level",`${t} ${n}`,v("level",n)),o+=f("source",b(e.source),v("source",e.source)),o+=f("processId",String(e.processId),v("processId",e.processId)),o+=f("threadId",String(e.threadId),v("threadId",e.threadId)),void 0!==e.pageId&&(o+=f("pageId",String(e.pageId),v("pageId",e.pageId))),o+=f("message",b(e.message)),e.error&&(o+=(a=e.error,`<div class="log-details-row"><span class="log-details-section-label">error (JSON):</span></div>\n<div class="log-details-row"><pre class="log-details-error">${b(JSON.stringify(a,null,2))}</pre></div>`)),void 0!==e.data&&(o+='<div class="log-details-row"><span class="log-details-section-label">data (JSON):</span></div>',o+=h(e.data)),o}function S(e,t,n,r){const o=`${e} ${n} ${"string"==typeof t?`"${t}"`:String(t)}`;return r?`${r} && ${o}`:o}var $=new class extends e{constructor(){super(),this._state={timerangeType:"relative",relativeTimerange:t,absoluteTimerangeFrom:0,absoluteTimerangeTo:0,autorefresh:!1,autorefreshInterval:5e3,filter:"",errorMessage:"",logs:[],metrics:{inputSize:0,chunksCount:0,outputSize:0,minTimestamp:Number.MAX_SAFE_INTEGER,maxTimestamp:0,logCount:0,reading:!0}},this._settings={relativeTimerange:n,autorefresh:r}}async _executeQuery(e){this._update({errorMessage:""});const t=new URLSearchParams;for(const a of Object.keys(e))t.set(a,e[a].toString());const n=await fetch("/query?"+t.toString());if(!n.ok){const e=await n.text()||`${n.status} ${n.statusText}`;return void this._update({errorMessage:e})}const{metrics:r,logs:o}=await n.json();return{metrics:r,logs:o}}async _checkInitialQuery(){const e=await this._executeQuery({});if(!e)return;const{metrics:t,logs:n}=e,r=Date.now(),o={absoluteTimerangeFrom:t.minTimestamp,absoluteTimerangeTo:r,metrics:t,logs:n};t.minTimestamp<=r-3e5&&(o.timerangeType="absolute"),this._update(o)}_onConnect(){this._checkInitialQuery()}_onInteraction(e,t){("autorefresh"in e||"autorefreshInterval"in e)&&this._autorefresh(e),void 0!==t&&this[t]()}async refresh_now(){const e={};"absolute"===this._state.timerangeType?(e.from=this._state.absoluteTimerangeFrom,e.to=this._state.absoluteTimerangeTo):e.from=Date.now()-this._state.relativeTimerange,this._state.filter&&(e.filter=this._state.filter);const t=await this._executeQuery(e);t&&this._update(t)}_autorefreshIntervalId;_startAutorefresh(){this._autorefreshIntervalId=setInterval(()=>{this.refresh_now()},this._state.autorefreshInterval)}_stopAutorefresh(){clearInterval(this._autorefreshIntervalId),this._autorefreshIntervalId=void 0}_autorefresh({autorefresh:e}){!1===e?this._stopAutorefresh():(!0===e||this._stopAutorefresh(),this._startAutorefresh())}};function T(){const e=document.querySelector("#filterInput"),t=function(e,t){let n;return(...r)=>{clearTimeout(n),n=setTimeout(()=>e(...r),t)}}(e=>$.interaction({filter:e}),250);e?.addEventListener("input",e=>{const n=e.target;t(n.value)}),document.querySelector("#timerangeTypeSelect")?.addEventListener("change",e=>{const t=e.detail;$.interaction({timerangeType:t.selectedOption.value})}),document.querySelector("#relativeTimerangeSelect")?.addEventListener("change",e=>{const t=e.detail;$.interaction({relativeTimerange:Number(t.selectedOption.value)})}),document.querySelector("#autorefreshSelect")?.addEventListener("change",e=>{const t=e.detail.selectedOption.value;"none"===t?$.interaction({autorefresh:!1}):$.interaction({autorefresh:!0,autorefreshInterval:Number(t)})}),document.querySelector("#absoluteFromPicker")?.addEventListener("change",e=>{const t=e.target;t.dateValue&&$.interaction({absoluteTimerangeFrom:t.dateValue.getTime()})}),document.querySelector("#absoluteToPicker")?.addEventListener("change",e=>{const t=e.target;t.dateValue&&$.interaction({absoluteTimerangeTo:t.dateValue.getTime()})}),document.querySelector("#refreshNowBtn")?.addEventListener("click",()=>$.interaction({action:"refresh_now"}))}function _(e){e.addEventListener("click",e=>{const t=e.target.closest("th[data-col]");if(!t)return;const n=t.dataset.col;if(!n)return;const r=$.state.logs,o=document.querySelector("#columnFilterPopover"),a=document.querySelector("#columnFilterPopoverContent");if(!o||!a)return;if("timestamp"===n){if(0===r.length)return;const e=r.map(e=>e.timestamp),t=Math.min(...e),n=Math.max(...e);return void $.interaction({timerangeType:"absolute",absoluteTimerangeFrom:t,absoluteTimerangeTo:n})}a.innerHTML="level"===n?["debug","info","warn","error","fatal"].map(e=>`<div class="column-filter-row"><ui5-checkbox data-filter-value="${e}" text="${e}"></ui5-checkbox></div>`).join(""):function(e,t){return[...new Set(t.map(t=>t[e]))].toSorted().map(e=>{const t=void 0===e;return`<div class="column-filter-row"><ui5-checkbox data-filter-value='${t?"__undefined__":JSON.stringify(e)}' text="${t?"none":String(e)}"></ui5-checkbox></div>`}).join("")}(n,r);const s=document.querySelector("#columnFilterApply"),i=s?.cloneNode(!0);s&&i&&(s.replaceWith(i),i.addEventListener("click",()=>function(e){const t=document.querySelector("#columnFilterPopoverContent");if(!t)return;const n=[...t.querySelectorAll("ui5-checkbox")].filter(e=>!0===e.checked);if(0===n.length)return;const r=n.map(e=>{const t=e.dataset.filterValue??"null";if("__undefined__"!==t)try{return JSON.parse(t)}catch{return t}}),o=r.map(t=>S(e,t,"===","")),a=r.length>1?`(${o.join(" || ")})`:o[0],s=$.state.filter?`${$.state.filter} && ${a}`:a;$.interaction({filter:s});const i=document.querySelector("#filterInput");i&&(i.value=s),document.querySelector("#columnFilterPopover").open=!1}("level"===n?"level":n))),o.opener=t,o.open=!0})}function k(){T(),document.querySelector("#statusBtn")?.addEventListener("click",e=>{const t=document.querySelector("#metricsPopover"),n=document.querySelector("#metricsPopoverContent");t&&n&&(n.innerHTML=function(){const e=$.state.metrics;return`<div class="log-details-row"><span class="log-details-label">Status</span>\n <span>${e.reading?"Live (reading)":"Replay (complete)"}</span></div>\n <div class="log-details-row"><span class="log-details-label">Log count</span>\n <span>${e.logCount}</span></div>\n <div class="log-details-row"><span class="log-details-label">Input size</span>\n <span>${g(e.inputSize)}</span></div>\n <div class="log-details-row"><span class="log-details-label">Output size</span>\n <span>${g(e.outputSize)}</span></div>\n <div class="log-details-row"><span class="log-details-label">Chunks</span>\n <span>${e.chunksCount}</span></div>\n <div class="log-details-row"><span class="log-details-label">From</span>\n <span>${p(e.minTimestamp)}</span></div>\n <div class="log-details-row"><span class="log-details-label">To</span>\n <span>${p(e.maxTimestamp)}</span></div>`}(),t.opener=e.currentTarget,t.open=!0)});const e=document.querySelector("#logTable thead");e&&_(e);const t=document.querySelector("#logTable tbody");t&&I(t),document.querySelector("#logDetailsClose")?.addEventListener("click",()=>{const e=document.querySelector("#logDetailsPopover");e&&(e.open=!1)})}function I(e){e.addEventListener("click",e=>{const t=e.target.closest("tr[data-index]");if(!t)return;const n=Number(t.dataset.index),r=$.state.logs[n];if(!r)return;const o=document.querySelector("#logDetailsPopover"),a=document.querySelector("#logDetailsPopoverContent");o&&a&&(a.innerHTML=y(r),function(e){for(const t of e.querySelectorAll(".filter-add-btn, .filter-remove-btn"))t.addEventListener("click",()=>{const e=t,n=e.dataset.field??"",r=e.dataset.value??"null",o=e.dataset.op??"===";let a;try{a=JSON.parse(r)}catch{a=r}const s=S(n,a,o,$.state.filter);$.interaction({filter:s});const i=document.querySelector("#filterInput");i&&(i.value=s)})}(a),o.opener=t,o.open=!0)})}var w=["timerangeType","relativeTimerange","autorefresh","autorefreshInterval","absoluteTimerangeFrom","absoluteTimerangeTo"];function q(e){if(w.some(t=>t in e)&&function(){const e=document.querySelector("#toolbar");e&&(e.innerHTML=i($.state,$.settings),T())}(),"metrics"in e&&function(){const e=document.querySelector("#statusBtn");if(!e)return;const t=$.state.metrics.reading;e.textContent=t?"Status: Live":"Status: Replay",e.setAttribute("design",t?"Positive":"Neutral")}(),"logs"in e&&function(){const e=document.querySelector("#logTable");if(!e)return;e.innerHTML=m($.state.logs);const t=e.querySelector("thead");t&&_(t);const n=e.querySelector("tbody");n&&I(n)}(),"errorMessage"in e&&function(){const e=document.querySelector("#toolbar");if(!e)return;const t=document.querySelector("#errorStrip");$.state.errorMessage?t?t.textContent=$.state.errorMessage:e.insertAdjacentHTML("beforeend",`<ui5-message-strip id="errorStrip" design="Negative" hide-close-button>${$.state.errorMessage}</ui5-message-strip>`):t?.remove()}(),"filter"in e){const e=document.querySelector("#filterInput");e&&document.activeElement!==e&&(e.value=$.state.filter)}}document.addEventListener("DOMContentLoaded",()=>{!function(){const e=document.querySelector("#app");e&&e.insertAdjacentHTML("beforebegin",'<ui5-popover id="metricsPopover" header-text="Metrics" placement="Bottom">\n <div id="metricsPopoverContent" style="padding: 0.5rem;"></div>\n </ui5-popover>\n <ui5-popover id="logDetailsPopover" header-text="Log Details" placement="Bottom" style="max-width: 600px;">\n <div id="logDetailsPopoverContent" style="padding: 0.5rem;"></div>\n <div slot="footer" style="display: flex; justify-content: flex-end; padding: 0.5rem;">\n <ui5-button id="logDetailsClose" design="Transparent">Close</ui5-button>\n </div>\n </ui5-popover>\n <ui5-popover id="columnFilterPopover" placement="Bottom">\n <div id="columnFilterPopoverContent" style="padding: 0.5rem; min-width: 180px;"></div>\n <div slot="footer" style="display: flex; justify-content: flex-end; padding: 0.5rem;">\n <ui5-button id="columnFilterApply">Filter</ui5-button>\n </div>\n </ui5-popover>')}(),$.connect(q),function(){const e=document.querySelector("#app");if(!e)return;const{state:t,settings:n}=$;e.innerHTML=function(e){const t=e.reading;return`<div id="header">\n <h1>UI5 Test Runner Log Viewer</h1>\n <ui5-button id="statusBtn" design="${t?"Positive":"Neutral"}">Status: ${t?"Live":"Replay"}</ui5-button>\n</div>`}(t.metrics)+`<div id="toolbar">${i(t,n)}</div><div id="logTableWrapper"><table id="logTable">${m(t.logs)}</table></div>`,k()}()})}();
|
|
2
|
+
!function(){var e=class{_state={};get state(){return this._state}_settings={};get settings(){return this._settings}_updateCb=()=>{throw new Error("UI not connected")};_update({...e}){for(const t of Object.keys(e))e[t]===this._state[t]&&delete e[t];return Object.assign(this._state,e),Object.keys(e).length>0&&this._updateCb({...e}),e}_onConnect(){}connect(e){this._updateCb=e,this._onConnect()}interaction(e){const{action:t,...n}=e,r=this._update(n);(void 0!==t||Object.keys(r).length>0)&&this._onInteraction(r,t)}},t=3e5,n=[{label:"5 minutes",key:t},{label:"15 minutes",key:9e5},{label:"30 minutes",key:18e5},{label:"1 hour",key:36e5},{label:"3 hours",key:108e5}],r=[{label:"5 seconds",key:5e3},{label:"10 seconds",key:1e4},{label:"30 seconds",key:3e4},{label:"60 seconds",key:6e4}];function o(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}function a(e){return e.toString().padStart(2,"0")}function s(e){const t=new Date(e);return`${a(t.getMonth()+1)}/${a(t.getDate())}/${t.getFullYear()}, ${a(t.getHours())}:${a(t.getMinutes())}:${a(t.getSeconds())}`}function i(e,t){const n="relative"===e.timerangeType,r=n?function(e,t){const n=t.relativeTimerange.map(t=>`<ui5-option value="${t.key}"${e.relativeTimerange===t.key?" selected":""}>${t.label}</ui5-option>`).join(""),r=t.autorefresh.map(t=>`<ui5-option value="${t.key}"${e.autorefreshInterval===t.key?" selected":""}>${t.label}</ui5-option>`).join("");return`<ui5-select id="relativeTimerangeSelect">${n}</ui5-select>\n <span>Auto Refresh:</span>\n <ui5-select id="autorefreshSelect">\n <ui5-option value="none"${e.autorefresh?"":" selected"}>None</ui5-option>\n ${r}\n </ui5-select>`}(e,t):function(e){return`<ui5-datetime-picker id="absoluteFromPicker" value="${s(e.absoluteTimerangeFrom)}" placeholder="From"></ui5-datetime-picker>\n <ui5-datetime-picker id="absoluteToPicker" value="${s(e.absoluteTimerangeTo)}" placeholder="To"></ui5-datetime-picker>`}(e),a=e.errorMessage?`<ui5-message-strip id="errorStrip" design="Negative" hide-close-button>${o(e.errorMessage)}</ui5-message-strip>`:"";return`<ui5-input id="filterInput" value="${o(e.filter)}" placeholder="Filter expression..." style="flex:1;min-width:200px;"></ui5-input>\n <ui5-select id="timerangeTypeSelect">\n <ui5-option value="relative"${n?" selected":""}>Relative</ui5-option>\n <ui5-option value="absolute"${n?"":" selected"}>Absolute</ui5-option>\n </ui5-select>\n ${r}\n <ui5-button id="refreshNowBtn">Refresh now</ui5-button>\n ${a}`}var l={0:"🔍",1:"💬",2:"⚠️",3:"❌",4:"💣"},c={0:"debug",1:"info",2:"warn",3:"error",4:"fatal"},u=["B","KB","MB","GB"];function d(e){return l[e]??"?"}function p(e){return e?new Date(e).toLocaleString():"—"}function m(e){let t=e,n=0;for(;t>=1024&&n<u.length-1;)t/=1024,n++;return`${0===n?t.toString():t.toFixed(1)} ${u[n]}`}function g(e){const t='<thead><tr>\n <th data-col="timestamp" class="col-clickable">Timestamp (local)</th>\n <th data-col="level" class="col-clickable">Level</th>\n <th data-col="source" class="col-clickable">Source</th>\n <th data-col="processId" class="col-clickable">PID</th>\n <th data-col="threadId" class="col-clickable">TID</th>\n <th data-col="pageId" class="col-clickable">Page ID</th>\n <th class="col-message">Message</th>\n</tr></thead>';return 0===e.length?`${t}<tbody><tr><td colspan="7" style="text-align:center;padding:1rem;color:var(--sapContent_LabelColor);">No log entries</td></tr></tbody>`:`${t}<tbody>${e.map((e,t)=>{const n=e.message.length>200?`${e.message.slice(0,200)}…`:e.message;return`<tr data-index="${t}">\n <td>${p(e.timestamp)}</td>\n <td>${d(e.level)}</td>\n <td>${e.source}</td>\n <td>${e.processId}</td>\n <td>${e.threadId}</td>\n <td>${e.pageId??""}</td>\n <td class="col-message">${r=n,r.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}</td>\n</tr>`;var r}).join("")}</tbody>`}function v(e,t){const n=JSON.stringify(t);return`<span class="log-details-actions">\n <ui5-button class="filter-add-btn" design="Transparent" data-field="${e}" data-value='${n}' data-op="===">➕</ui5-button>\n <ui5-button class="filter-remove-btn" design="Transparent" data-field="${e}" data-value='${n}' data-op="!==">➖</ui5-button>\n </span>`}function f(e,t,n=""){return`<div class="log-details-row">\n <span class="log-details-label">${e}</span>\n <span class="log-details-value">${t}</span>\n ${n}\n</div>`}function h(e,t="data"){return Object.entries(e).map(([e,n])=>{const r=`${t}.${e}`;return Array.isArray(n)?n.map((e,t)=>{const n=`${r}[${t}]`;return"object"==typeof e&&null!==e?h(e,n):f(n,b(String(e)),v(n,e))}).join(""):"object"==typeof n&&null!==n?h(n,r):f(r,b(String(n)),v(r,n))}).join("")}function b(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}function y(e){const t=d(e.level),n=(r=e.level,c[r]??"unknown");var r;let o=f("timestamp",p(e.timestamp));var a;return o+=f("level",`${t} ${n}`,v("level",n)),o+=f("source",b(e.source),v("source",e.source)),o+=f("processId",String(e.processId),v("processId",e.processId)),o+=f("threadId",String(e.threadId),v("threadId",e.threadId)),void 0!==e.pageId&&(o+=f("pageId",String(e.pageId),v("pageId",e.pageId))),o+=f("message",b(e.message)),e.error&&(o+=(a=e.error,`<div class="log-details-row"><span class="log-details-section-label">error (JSON):</span></div>\n<div class="log-details-row"><pre class="log-details-error">${b(JSON.stringify(a,null,2))}</pre></div>`)),void 0!==e.data&&(o+='<div class="log-details-row"><span class="log-details-section-label">data (JSON):</span></div>',o+=h(e.data)),o}function S(e,t,n,r){const o=`${e} ${n} ${"string"==typeof t?`"${t}"`:String(t)}`;return r?`${r} && ${o}`:o}var $=new class extends e{constructor(){super(),this._state={timerangeType:"relative",relativeTimerange:t,absoluteTimerangeFrom:0,absoluteTimerangeTo:0,autorefresh:!1,autorefreshInterval:5e3,filter:"",errorMessage:"",logs:[],metrics:{inputSize:0,chunksCount:0,outputSize:0,minTimestamp:Number.MAX_SAFE_INTEGER,maxTimestamp:0,logCount:0,reading:!0}},this._settings={relativeTimerange:n,autorefresh:r}}async _executeQuery(e){this._update({errorMessage:""});const t=new URLSearchParams;for(const a of Object.keys(e))t.set(a,e[a].toString());const n=await fetch("/query?"+t.toString());if(!n.ok){const e=await n.text()||`${n.status} ${n.statusText}`;return void this._update({errorMessage:e})}const{metrics:r,logs:o}=await n.json();return{metrics:r,logs:o}}async _checkInitialQuery(){const e=await this._executeQuery({});if(!e)return;const{metrics:t,logs:n}=e,r=Date.now(),o={absoluteTimerangeFrom:t.minTimestamp,absoluteTimerangeTo:r,metrics:t,logs:n,...t.minTimestamp<=r-3e5&&{timerangeType:"absolute"}};this._update(o)}_onConnect(){this._checkInitialQuery()}_onInteraction(e,t){("autorefresh"in e||"autorefreshInterval"in e)&&this._autorefresh(e),void 0!==t&&this[t]()}async refresh_now(){const e={};"absolute"===this._state.timerangeType?(e.from=this._state.absoluteTimerangeFrom,e.to=this._state.absoluteTimerangeTo):e.from=Date.now()-this._state.relativeTimerange,this._state.filter&&(e.filter=this._state.filter);const t=await this._executeQuery(e);t&&this._update(t)}_autorefreshIntervalId;_startAutorefresh(){this._autorefreshIntervalId=setInterval(()=>{this.refresh_now()},this._state.autorefreshInterval)}_stopAutorefresh(){clearInterval(this._autorefreshIntervalId),this._autorefreshIntervalId=void 0}_autorefresh({autorefresh:e}){!1===e?this._stopAutorefresh():(!0===e||this._stopAutorefresh(),this._startAutorefresh())}};function T(){const e=document.querySelector("#filterInput"),t=function(e,t){let n;return(...r)=>{clearTimeout(n),n=setTimeout(()=>e(...r),t)}}(e=>$.interaction({filter:e}),250);e?.addEventListener("input",e=>{const n=e.target;t(n.value)}),document.querySelector("#timerangeTypeSelect")?.addEventListener("change",e=>{const t=e.detail;$.interaction({timerangeType:t.selectedOption.value})}),document.querySelector("#relativeTimerangeSelect")?.addEventListener("change",e=>{const t=e.detail;$.interaction({relativeTimerange:Number(t.selectedOption.value)})}),document.querySelector("#autorefreshSelect")?.addEventListener("change",e=>{const t=e.detail.selectedOption.value;"none"===t?$.interaction({autorefresh:!1}):$.interaction({autorefresh:!0,autorefreshInterval:Number(t)})}),document.querySelector("#absoluteFromPicker")?.addEventListener("change",e=>{const t=e.target;t.dateValue&&$.interaction({absoluteTimerangeFrom:t.dateValue.getTime()})}),document.querySelector("#absoluteToPicker")?.addEventListener("change",e=>{const t=e.target;t.dateValue&&$.interaction({absoluteTimerangeTo:t.dateValue.getTime()})}),document.querySelector("#refreshNowBtn")?.addEventListener("click",()=>$.interaction({action:"refresh_now"}))}function _(e){e.addEventListener("click",e=>{const t=e.target.closest("th[data-col]");if(!t)return;const n=t.dataset.col;if(!n)return;const r=$.state.logs,o=document.querySelector("#columnFilterPopover"),a=document.querySelector("#columnFilterPopoverContent");if(!o||!a)return;if("timestamp"===n){if(0===r.length)return;const e=r.map(e=>e.timestamp),t=Math.min(...e),n=Math.max(...e);return void $.interaction({timerangeType:"absolute",absoluteTimerangeFrom:t,absoluteTimerangeTo:n})}a.innerHTML="level"===n?["debug","info","warn","error","fatal"].map(e=>`<div class="column-filter-row"><ui5-checkbox data-filter-value="${e}" text="${e}"></ui5-checkbox></div>`).join(""):function(e,t){return[...new Set(t.map(t=>t[e]))].toSorted().map(e=>{const t=void 0===e;return`<div class="column-filter-row"><ui5-checkbox data-filter-value='${t?"__undefined__":JSON.stringify(e)}' text="${t?"none":String(e)}"></ui5-checkbox></div>`}).join("")}(n,r);const s=document.querySelector("#columnFilterApply"),i=s?.cloneNode(!0);s&&i&&(s.replaceWith(i),i.addEventListener("click",()=>function(e){const t=document.querySelector("#columnFilterPopoverContent");if(!t)return;const n=[...t.querySelectorAll("ui5-checkbox")].filter(e=>e.checked);if(0===n.length)return;const r=n.map(e=>{const t=e.dataset.filterValue??"null";if("__undefined__"!==t)try{return JSON.parse(t)}catch{return t}}),o=r.map(t=>S(e,t,"===","")),a=r.length>1?`(${o.join(" || ")})`:o[0],s=$.state.filter?`${$.state.filter} && ${a}`:a;$.interaction({filter:s});const i=document.querySelector("#filterInput");i&&(i.value=s),document.querySelector("#columnFilterPopover").open=!1}("level"===n?"level":n))),o.opener=t,o.open=!0})}function k(){T(),document.querySelector("#statusBtn")?.addEventListener("click",e=>{const t=document.querySelector("#metricsPopover"),n=document.querySelector("#metricsPopoverContent");t&&n&&(n.innerHTML=function(){const e=$.state.metrics;return`<div class="log-details-row"><span class="log-details-label">Status</span>\n <span>${e.reading?"Live (reading)":"Replay (complete)"}</span></div>\n <div class="log-details-row"><span class="log-details-label">Log count</span>\n <span>${e.logCount}</span></div>\n <div class="log-details-row"><span class="log-details-label">Input size</span>\n <span>${m(e.inputSize)}</span></div>\n <div class="log-details-row"><span class="log-details-label">Output size</span>\n <span>${m(e.outputSize)}</span></div>\n <div class="log-details-row"><span class="log-details-label">Chunks</span>\n <span>${e.chunksCount}</span></div>\n <div class="log-details-row"><span class="log-details-label">From</span>\n <span>${p(e.minTimestamp)}</span></div>\n <div class="log-details-row"><span class="log-details-label">To</span>\n <span>${p(e.maxTimestamp)}</span></div>`}(),t.opener=e.currentTarget,t.open=!0)});const e=document.querySelector("#logTable thead");e&&_(e);const t=document.querySelector("#logTable tbody");t&&I(t),document.querySelector("#logDetailsClose")?.addEventListener("click",()=>{const e=document.querySelector("#logDetailsPopover");e&&(e.open=!1)})}function I(e){e.addEventListener("click",e=>{const t=e.target.closest("tr[data-index]");if(!t)return;const n=Number(t.dataset.index),r=$.state.logs[n];if(!r)return;const o=document.querySelector("#logDetailsPopover"),a=document.querySelector("#logDetailsPopoverContent");o&&a&&(a.innerHTML=y(r),function(e){for(const t of e.querySelectorAll(".filter-add-btn, .filter-remove-btn"))t.addEventListener("click",()=>{const e=t,n=e.dataset.field??"",r=e.dataset.value??"null",o=e.dataset.op??"===";let a;try{a=JSON.parse(r)}catch{a=r}const s=S(n,a,o,$.state.filter);$.interaction({filter:s});const i=document.querySelector("#filterInput");i&&(i.value=s)})}(a),o.opener=t,o.open=!0)})}var w=["timerangeType","relativeTimerange","autorefresh","autorefreshInterval","absoluteTimerangeFrom","absoluteTimerangeTo"];function q(e){if(w.some(t=>Object.hasOwn(e,t))&&function(){const e=document.querySelector("#toolbar");e&&(e.innerHTML=i($.state,$.settings),T())}(),"metrics"in e&&function(){const e=document.querySelector("#statusBtn");if(!e)return;const t=$.state.metrics.reading;e.textContent=t?"Status: Live":"Status: Replay",e.setAttribute("design",t?"Positive":"Neutral")}(),"logs"in e&&function(){const e=document.querySelector("#logTable");if(!e)return;e.innerHTML=g($.state.logs);const t=e.querySelector("thead");t&&_(t);const n=e.querySelector("tbody");n&&I(n)}(),"errorMessage"in e&&function(){const e=document.querySelector("#toolbar");if(!e)return;const t=document.querySelector("#errorStrip");$.state.errorMessage?t?t.textContent=$.state.errorMessage:e.insertAdjacentHTML("beforeend",`<ui5-message-strip id="errorStrip" design="Negative" hide-close-button>${$.state.errorMessage}</ui5-message-strip>`):t?.remove()}(),!("filter"in e))return;const t=document.querySelector("#filterInput");t&&document.activeElement!==t&&(t.value=$.state.filter)}document.addEventListener("DOMContentLoaded",()=>{!function(){const e=document.querySelector("#app");e&&e.insertAdjacentHTML("beforebegin",'<ui5-popover id="metricsPopover" header-text="Metrics" placement="Bottom">\n <div id="metricsPopoverContent" style="padding: 0.5rem;"></div>\n </ui5-popover>\n <ui5-popover id="logDetailsPopover" header-text="Log Details" placement="Bottom" style="max-width: 600px;">\n <div id="logDetailsPopoverContent" style="padding: 0.5rem;"></div>\n <div slot="footer" style="display: flex; justify-content: flex-end; padding: 0.5rem;">\n <ui5-button id="logDetailsClose" design="Transparent">Close</ui5-button>\n </div>\n </ui5-popover>\n <ui5-popover id="columnFilterPopover" placement="Bottom">\n <div id="columnFilterPopoverContent" style="padding: 0.5rem; min-width: 180px;"></div>\n <div slot="footer" style="display: flex; justify-content: flex-end; padding: 0.5rem;">\n <ui5-button id="columnFilterApply">Filter</ui5-button>\n </div>\n </ui5-popover>')}(),$.connect(q),function(){const e=document.querySelector("#app");if(!e)return;const{state:t,settings:n}=$;e.innerHTML=function(e){const t=e.reading;return`<div id="header">\n <h1>UI5 Test Runner Log Viewer</h1>\n <ui5-button id="statusBtn" design="${t?"Positive":"Neutral"}">Status: ${t?"Live":"Replay"}</ui5-button>\n</div>`}(t.metrics)+`<div id="toolbar">${i(t,n)}</div><div id="logTableWrapper"><table id="logTable">${g(t.logs)}</table></div>`,k()}()})}();
|
|
@@ -15,10 +15,8 @@ export const Folder = {
|
|
|
15
15
|
try {
|
|
16
16
|
await FileSystem.mkdir(path, recursive);
|
|
17
17
|
}
|
|
18
|
-
catch (
|
|
19
|
-
|
|
20
|
-
error.cause = error_;
|
|
21
|
-
throw error;
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error(`Failed to create folder: ${path}`, { cause: error });
|
|
22
20
|
}
|
|
23
21
|
},
|
|
24
22
|
async recreate(path) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Exit, FileSystem, assert } from '../../platform/index.js';
|
|
2
|
-
import { setTimeout } from 'node:timers/promises';
|
|
1
|
+
import { Exit, FileSystem, Process, assert } from '../../platform/index.js';
|
|
3
2
|
export class FramedStreamReader {
|
|
4
3
|
static create(fileName, pollIntervalMs = 500) {
|
|
5
4
|
return new this(fileName, pollIntervalMs);
|
|
@@ -33,7 +32,7 @@ export class FramedStreamReader {
|
|
|
33
32
|
_reading = true;
|
|
34
33
|
_buffer = Buffer.alloc(0);
|
|
35
34
|
_task;
|
|
36
|
-
async
|
|
35
|
+
async *#read(end) {
|
|
37
36
|
const fileStream = FileSystem.createReadStream(this._fileName, {
|
|
38
37
|
start: this._startPos,
|
|
39
38
|
end,
|
|
@@ -76,10 +75,10 @@ export class FramedStreamReader {
|
|
|
76
75
|
while (this._reading) {
|
|
77
76
|
const stats = await FileSystem.stat(this._fileName);
|
|
78
77
|
if (stats.size > this._startPos) {
|
|
79
|
-
yield* this
|
|
78
|
+
yield* this.#read(stats.size - 1);
|
|
80
79
|
}
|
|
81
80
|
if (this._reading) {
|
|
82
|
-
await Promise.race([
|
|
81
|
+
await Promise.race([Process.sleep(this._pollIntervalMs), abortSignal]);
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
84
|
}
|
|
@@ -13,12 +13,13 @@ export class FramedStreamWriter {
|
|
|
13
13
|
await promise;
|
|
14
14
|
}
|
|
15
15
|
async write(buffer) {
|
|
16
|
-
if (buffer.length
|
|
17
|
-
|
|
18
|
-
size.writeUInt32BE(buffer.length, 0);
|
|
19
|
-
await this._write(size);
|
|
20
|
-
await this._write(buffer);
|
|
16
|
+
if (buffer.length === 0) {
|
|
17
|
+
return;
|
|
21
18
|
}
|
|
19
|
+
const size = Buffer.alloc(4);
|
|
20
|
+
size.writeUInt32BE(buffer.length, 0);
|
|
21
|
+
await this._write(size);
|
|
22
|
+
await this._write(buffer);
|
|
22
23
|
}
|
|
23
24
|
async end() {
|
|
24
25
|
await this._write(Buffer.from([0, 0, 0, 0]));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Host } from '../../platform/Host.js';
|
|
2
|
+
import { isSecretValue } from '../../types/ISecretValue.js';
|
|
3
|
+
const redactSecretsReplacer = (_, value) => (isSecretValue(value) ? '***' : value);
|
|
4
|
+
export function redactSecrets(object) {
|
|
5
|
+
return JSON.parse(JSON.stringify(object, redactSecretsReplacer));
|
|
6
|
+
}
|
|
7
|
+
export function anonymize(object) {
|
|
8
|
+
const home = Host.homedir();
|
|
9
|
+
const json = JSON.stringify(redactSecrets(object));
|
|
10
|
+
return json.includes(home) ? JSON.parse(json.replaceAll(home, '~')) : object;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const getEffectiveTimeout = (itemTimeout = 0, globalTimeout = 0, startTime) => {
|
|
2
|
+
if (itemTimeout || globalTimeout) {
|
|
3
|
+
if (globalTimeout) {
|
|
4
|
+
const remainingGlobal = globalTimeout - (Date.now() - startTime);
|
|
5
|
+
return itemTimeout ? Math.min(remainingGlobal, itemTimeout) : remainingGlobal;
|
|
6
|
+
}
|
|
7
|
+
return itemTimeout;
|
|
8
|
+
}
|
|
9
|
+
return 0;
|
|
10
|
+
};
|
|
11
|
+
export const isGloballyTimedOut = (globalTimeout = 0, startTime) => globalTimeout > 0 && Date.now() - startTime > globalTimeout;
|
|
@@ -23,18 +23,22 @@ export class TestReportBuilder {
|
|
|
23
23
|
this._suites[pageUrl] = [...parentUrls, suiteUrl];
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
merge(url, testResults) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
merge(url, testResults, extra) {
|
|
27
|
+
if (testResults.tool.version && !this._report.results.tool.extra?.['qunitVersion']) {
|
|
28
|
+
this._report.results.tool.extra = {
|
|
29
|
+
...this._report.results.tool.extra,
|
|
30
|
+
qunitVersion: testResults.tool.version
|
|
31
|
+
};
|
|
30
32
|
}
|
|
31
33
|
const { results } = this._report;
|
|
32
34
|
const suites = [...(this._suites[url] ?? []), url];
|
|
33
35
|
for (const test of testResults.tests) {
|
|
34
|
-
|
|
36
|
+
const resolvedTest = {
|
|
35
37
|
...test,
|
|
36
|
-
suite: [...suites, ...(test.suite ?? [])]
|
|
37
|
-
|
|
38
|
+
suite: [...suites, ...(test.suite ?? [])],
|
|
39
|
+
...((extra || test.extra) && { extra: { ...test.extra, ...extra } })
|
|
40
|
+
};
|
|
41
|
+
results.tests.push(resolvedTest);
|
|
38
42
|
}
|
|
39
43
|
for (const summaryField of SUMMARY_FIELDS) {
|
|
40
44
|
results.summary[summaryField] += testResults.summary[summaryField];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const countCpuModels = (cpus) => {
|
|
2
|
+
const counts = {};
|
|
3
|
+
for (const { model } of cpus) {
|
|
4
|
+
counts[model] = (counts[model] ?? 0) + 1;
|
|
5
|
+
}
|
|
6
|
+
return Object.entries(counts).map(([model, count]) => ({ model, count: count }));
|
|
7
|
+
};
|
|
8
|
+
export const formatHostLabel = ({ machine, cpus }) => {
|
|
9
|
+
const entries = countCpuModels(cpus).map(({ model, count }) => (count === 1 ? model : `${count}x ${model}`));
|
|
10
|
+
return entries.length > 0 ? `${machine} / ${entries.join(', ')}` : machine;
|
|
11
|
+
};
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
const literalPrototype = Object.getPrototypeOf({});
|
|
2
2
|
export const toPlainObject = (object) => {
|
|
3
3
|
const prototype = Object.getPrototypeOf(object);
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
return Object.getPrototypeOf(object) === literalPrototype
|
|
5
|
+
? object
|
|
6
|
+
: Object.assign({}, toPlainObject(prototype), object);
|
|
7
|
+
};
|
|
8
|
+
export const objectValues = (object) => Object.values(object).filter((v) => v !== undefined);
|
|
9
|
+
export const pick = (object, keys) => {
|
|
10
|
+
const result = {};
|
|
11
|
+
for (const key of keys) {
|
|
12
|
+
if (key in object) {
|
|
13
|
+
result[key] = object[key];
|
|
14
|
+
}
|
|
6
15
|
}
|
|
7
|
-
return
|
|
16
|
+
return result;
|
|
8
17
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function validate(value, schema) {
|
|
2
|
+
if (value === null || typeof value !== 'object') {
|
|
3
|
+
throw new TypeError(`Expected an object, got ${typeof value}`);
|
|
4
|
+
}
|
|
5
|
+
const object = value;
|
|
6
|
+
for (const [key, schemaValue] of Object.entries(schema)) {
|
|
7
|
+
if (!(key in object)) {
|
|
8
|
+
throw new TypeError(`Missing required property: "${key}"`);
|
|
9
|
+
}
|
|
10
|
+
if (typeof schemaValue === 'string') {
|
|
11
|
+
const actualType = typeof object[key];
|
|
12
|
+
if (actualType !== schemaValue) {
|
|
13
|
+
throw new TypeError(`Property "${key}" has type "${actualType}", expected "${schemaValue}"`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
validate(object[key], schemaValue);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -5,13 +5,9 @@ export const toIError = (error) => {
|
|
|
5
5
|
const attributes = {
|
|
6
6
|
name: error.name,
|
|
7
7
|
message: error.message,
|
|
8
|
-
stack: error.stack
|
|
8
|
+
stack: error.stack,
|
|
9
|
+
...(error.cause ? { cause: toIError(error.cause) } : {}),
|
|
10
|
+
...(error instanceof AggregateError && { errors: error.errors.map((item) => toIError(item)) })
|
|
9
11
|
};
|
|
10
|
-
if (error.cause) {
|
|
11
|
-
attributes.cause = toIError(error.cause);
|
|
12
|
-
}
|
|
13
|
-
if (error instanceof AggregateError) {
|
|
14
|
-
attributes.errors = error.errors.map((item) => toIError(item));
|
|
15
|
-
}
|
|
16
12
|
return attributes;
|
|
17
13
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const parseVersion = (version) => {
|
|
2
|
+
const [release, prerelease] = version.split('-', 2);
|
|
3
|
+
if (release) {
|
|
4
|
+
const releaseParts = release.split('.').map(Number);
|
|
5
|
+
const prereleaseParts = prerelease
|
|
6
|
+
? prerelease.split('.').map((part) => (Number.isNaN(Number(part)) ? part : Number(part)))
|
|
7
|
+
: [];
|
|
8
|
+
return [releaseParts, prereleaseParts];
|
|
9
|
+
}
|
|
10
|
+
return [[], []];
|
|
11
|
+
};
|
|
12
|
+
export const compareVersions = (version1, version2) => {
|
|
13
|
+
const [v1Release, v1Prerelease] = parseVersion(version1);
|
|
14
|
+
const [v2Release, v2Prerelease] = parseVersion(version2);
|
|
15
|
+
const maxLength = Math.max(v1Release.length, v2Release.length);
|
|
16
|
+
while (v1Release.length < maxLength) {
|
|
17
|
+
v1Release.push(0);
|
|
18
|
+
}
|
|
19
|
+
while (v2Release.length < maxLength) {
|
|
20
|
+
v2Release.push(0);
|
|
21
|
+
}
|
|
22
|
+
for (let index = 0; index < maxLength; index++) {
|
|
23
|
+
if (v1Release[index] > v2Release[index])
|
|
24
|
+
return 1;
|
|
25
|
+
if (v1Release[index] < v2Release[index])
|
|
26
|
+
return -1;
|
|
27
|
+
}
|
|
28
|
+
return comparePreReleases(v1Prerelease, v2Prerelease);
|
|
29
|
+
};
|
|
30
|
+
const comparePreReleases = (v1Prerelease, v2Prerelease) => {
|
|
31
|
+
if (v1Prerelease.length === 0 && v2Prerelease.length === 0) {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
if (v1Prerelease.length === 0)
|
|
35
|
+
return 1;
|
|
36
|
+
if (v2Prerelease.length === 0)
|
|
37
|
+
return -1;
|
|
38
|
+
for (let index = 0; index < Math.max(v1Prerelease.length, v2Prerelease.length); index++) {
|
|
39
|
+
const p1 = v1Prerelease[index] ?? 0;
|
|
40
|
+
const p2 = v2Prerelease[index] ?? 0;
|
|
41
|
+
const p1Type = typeof p1;
|
|
42
|
+
const p2Type = typeof p2;
|
|
43
|
+
if (p1Type !== p2Type) {
|
|
44
|
+
return p1Type === 'number' ? -1 : 1;
|
|
45
|
+
}
|
|
46
|
+
if (p1 > p2)
|
|
47
|
+
return 1;
|
|
48
|
+
if (p1 < p2)
|
|
49
|
+
return -1;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui5-test-runner",
|
|
3
|
-
"version": "6.0.0
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Standalone test runner for UI5",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,42 +17,6 @@
|
|
|
17
17
|
"engines": {
|
|
18
18
|
"node": ">=24.11.1"
|
|
19
19
|
},
|
|
20
|
-
"scripts": {
|
|
21
|
-
"ts-run": "node --no-warnings --experimental-strip-types --import ./src/platform/js2ts.mjs",
|
|
22
|
-
"cli": "make precli && npm run ts-run -- src/cli.ts",
|
|
23
|
-
"test:cli1": "npm run cli -- --url https://ui5.sap.com/test-resources/sap/m/demokit/cart/webapp/test/testsuite.qunit.html --report-dir tmp",
|
|
24
|
-
"test:cli2": "npm run cli -- --url \"https://ui5.sap.com/test-resources/sap/m/demokit/cart/webapp/test/Test.qunit.html?testsuite=test-resources/sap/ui/demo/cart/testsuite.qunit&test=unit/unitTests\" --report-dir tmp",
|
|
25
|
-
"test:cli3": "npm run cli -- --url \"https://ui5.sap.com/test-resources/sap/m/demokit/cart/webapp/test/Test.qunit.html?testsuite=test-resources%2Fsap%2Fui%2Fdemo%2Fcart%2Ftestsuite.qunit&test=integration%2FopaTestsComponent#/categories\" --report-dir tmp",
|
|
26
|
-
"test:cli4": "npm run cli -- --url \"https://ui5.sap.com/test-resources/sap/m/demokit/cart/webapp/test/Test.qunit.html?testsuite=test-resources/sap/ui/demo/cart/testsuite.qunit&test=integration/opaTestsIFrame\" --report-dir tmp",
|
|
27
|
-
"test:cli5": "npm run cli -- --cwd test/sample.js",
|
|
28
|
-
"test:openui5": "npm run cli -- --url http://localhost:8000/test-resources/sap/ui/core/qunit/testsuite.qunit.html --parallel 4",
|
|
29
|
-
"test:openui5.uncaught": "npm run cli -- --url \"http://localhost:8000/resources/sap/ui/test/starter/Test.qunit.html?testsuite=test-resources/sap/ui/core/qunit/odata/v2/testsuite.odatav2.qunit&test=ODataV2TreeBindingFlat_MockSrv\"",
|
|
30
|
-
"lint": "dpdm src/cli.ts --circular --tree false --warning false --skip-dynamic-imports circular && eslint \"**/*.mjs\" \"./src/**/*.ts\" --fix --cache --cache-strategy content && tsc --build --noEmit",
|
|
31
|
-
"build:options": "node build/options.mjs > ./src/configuration/options.ts && npm run lint",
|
|
32
|
-
"build:agent": "vite build",
|
|
33
|
-
"build:ui:report": "vite build -c src/ui/report/vite.config.mjs",
|
|
34
|
-
"start:ui:report": "vite -c src/ui/report/vite.config.mjs",
|
|
35
|
-
"build:ui:log": "vite build -c src/ui/log/vite.config.mjs",
|
|
36
|
-
"build:ui:lib": "vite build -c src/ui/lib/vite.config.mjs",
|
|
37
|
-
"start:ui:log": "vite -c src/ui/log/vite.config.mjs",
|
|
38
|
-
"test": "npm run lint && npm run test:unit && npm run test:e2e",
|
|
39
|
-
"test:samples": "npm run test:samples:js && npm run test:samples:ts",
|
|
40
|
-
"test:samples:js": "npm run test:sample:js:legacy && npm run test:sample:js:coverage:legacy && npm run test:sample:js:legacy-remote && npm run test:sample:js:coverage:legacy-remote && npm run test:sample:js:remote && npm run test:sample:js:coverage:remote && npm run test:sample:js:basic-authent && npm run test:sample:js:legacy:split-opa && npm run test:sample:js:remote:split-opa",
|
|
41
|
-
"test:coverall": "rimraf .nyc_output && jest --coverageDirectory .nyc_output --coverageReporters json && nyc --silent --no-clean npm run test:e2e && nyc merge .nyc_output .nyc_output/final/coverage.json && nyc report --temp-dir .nyc_output/final/ --report-dir coverage --branches 80 --functions 80 --lines 80 --statements 80",
|
|
42
|
-
"test:unit": "vitest run --coverage",
|
|
43
|
-
"test:unit:watch": "vitest",
|
|
44
|
-
"test:unit:debug": "node --inspect node_modules/jest/bin/jest.js --runInBand --no-coverage",
|
|
45
|
-
"pretest:e2e0": "npm install -g puppeteer selenium-webdriver playwright webdriverio",
|
|
46
|
-
"test:e2e0": "npm run cli -- --batch \"test/e2e/[\\w_]*\\.json\" --report-dir e2e --start \"node test/e2e/serve.js\" --start-wait-url http://localhost:8081 --start-wait-method HEAD --start-timeout 30s",
|
|
47
|
-
"test:e2e": "echo No E2E tests until the runner is ready",
|
|
48
|
-
"test:report": "node ./src/defaults/report.js ./test/report && reserve --config ./test/report/reserve.json",
|
|
49
|
-
"test:text-report": "node ./src/defaults/text-report.js ./test/report",
|
|
50
|
-
"build:doc": "node build/doc",
|
|
51
|
-
"build": "make precli && npx tsc --project tsconfig.cli.json",
|
|
52
|
-
"clean": "npm uninstall -g ui5-test-runner puppeteer nyc selenium-webdriver playwright webdriverio jsdom",
|
|
53
|
-
"update": "ncu -i --format group",
|
|
54
|
-
"semantic-release": "semantic-release"
|
|
55
|
-
},
|
|
56
20
|
"repository": {
|
|
57
21
|
"type": "git",
|
|
58
22
|
"url": "git+https://github.com/ArnaudBuchholz/ui5-test-runner.git"
|
|
@@ -74,46 +38,6 @@
|
|
|
74
38
|
"homepage": "https://github.com/ArnaudBuchholz/ui5-test-runner#readme",
|
|
75
39
|
"dependencies": {
|
|
76
40
|
"punyexpr": "1.3.1",
|
|
77
|
-
"reserve": "2.3.
|
|
78
|
-
},
|
|
79
|
-
"devDependencies": {
|
|
80
|
-
"@openui5/types": "^1.148.0",
|
|
81
|
-
"@stylistic/eslint-plugin": "^5.10.0",
|
|
82
|
-
"@stylistic/eslint-plugin-js": "^4.4.1",
|
|
83
|
-
"@types/node": "^24.12.2",
|
|
84
|
-
"@types/qunit": "^2.19.14",
|
|
85
|
-
"@ui5/cli": "^4.0.55",
|
|
86
|
-
"@ui5/middleware-code-coverage": "^2.0.3",
|
|
87
|
-
"@ui5/webcomponents": "^2.22.0",
|
|
88
|
-
"@ui5/webcomponents-fiori": "^2.22.0",
|
|
89
|
-
"@vitest/coverage-v8": "^4.1.8",
|
|
90
|
-
"baseline-browser-mapping": "^2.10.33",
|
|
91
|
-
"dotenv": "^17.4.2",
|
|
92
|
-
"dpdm": "^4.2.0",
|
|
93
|
-
"eslint": "^9.39.2",
|
|
94
|
-
"eslint-config-prettier": "^10.1.8",
|
|
95
|
-
"eslint-plugin-import": "^2.32.0",
|
|
96
|
-
"eslint-plugin-no-only-tests": "^3.4.0",
|
|
97
|
-
"eslint-plugin-prettier": "^5.5.6",
|
|
98
|
-
"eslint-plugin-security": "^4.0.0",
|
|
99
|
-
"eslint-plugin-sonarjs": "^4.0.3",
|
|
100
|
-
"eslint-plugin-unicorn": "^64.0.0",
|
|
101
|
-
"globals": "^17.6.0",
|
|
102
|
-
"jsdom": "^29.1.1",
|
|
103
|
-
"markdownlint-cli": "^0.48.0",
|
|
104
|
-
"npm-check-updates": "^22.2.2",
|
|
105
|
-
"puppeteer": "^25.1.0",
|
|
106
|
-
"qunit": "^2.26.0",
|
|
107
|
-
"rimraf": "^6.1.3",
|
|
108
|
-
"semantic-release": "^25.0.3",
|
|
109
|
-
"stylelint": "^17.12.0",
|
|
110
|
-
"stylelint-config-standard": "^40.0.0",
|
|
111
|
-
"terser": "^5.48.0",
|
|
112
|
-
"typescript": "^6.0.3",
|
|
113
|
-
"typescript-eslint": "^8.60.1",
|
|
114
|
-
"ui5-tooling-transpile": "^3.11.2",
|
|
115
|
-
"vite": "^8.0.16",
|
|
116
|
-
"vite-plugin-css-injected-by-js": "^5.0.1",
|
|
117
|
-
"vitest": "^4.1.8"
|
|
41
|
+
"reserve": "2.3.8"
|
|
118
42
|
}
|
|
119
43
|
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { OptionValidationError } from '../OptionValidationError.js';
|
|
2
|
-
const KNOWN_BROWSERS = new Set(['puppeteer']);
|
|
3
|
-
export const browser = (option, value) => {
|
|
4
|
-
if (typeof value === 'string') {
|
|
5
|
-
const name = value.startsWith('$/') && value.endsWith('.js') ? value.slice(2, -3) : value;
|
|
6
|
-
if (KNOWN_BROWSERS.has(name)) {
|
|
7
|
-
return name;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
throw new OptionValidationError(option);
|
|
11
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { logger } from '../../platform/index.js';
|
|
2
|
-
export const buildREserveConfiguration = (configuration) => {
|
|
3
|
-
const match = /\/((?:test-)?resources\/.*)/;
|
|
4
|
-
let { ui5 } = configuration;
|
|
5
|
-
if (!ui5.endsWith('/')) {
|
|
6
|
-
ui5 += '/';
|
|
7
|
-
}
|
|
8
|
-
const mappingUrl = new URL('$1', ui5).toString();
|
|
9
|
-
return {
|
|
10
|
-
port: configuration.port ?? 0,
|
|
11
|
-
mappings: [
|
|
12
|
-
{
|
|
13
|
-
method: 'GET,HEAD',
|
|
14
|
-
match,
|
|
15
|
-
url: mappingUrl,
|
|
16
|
-
'ignore-unverifiable-certificate': true
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
match: /^\/(.*)/,
|
|
20
|
-
cwd: configuration.webapp,
|
|
21
|
-
file: '$1'
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
custom: (request) => logger.debug({ source: 'server', message: 'Unhandled request', data: { url: request.url } })
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
status: 404
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
};
|
|
31
|
-
};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { assert, Exit, logger, Thread } from '../../platform/index.js';
|
|
2
|
-
import { serve } from 'reserve';
|
|
3
|
-
import { toPlainObject } from '../../utils/shared/object.js';
|
|
4
|
-
import { buildREserveConfiguration } from './REserve.js';
|
|
5
|
-
let channel;
|
|
6
|
-
let serverWorker;
|
|
7
|
-
let stopping = false;
|
|
8
|
-
export const server = {
|
|
9
|
-
async start(configuration) {
|
|
10
|
-
assert(serverWorker === undefined);
|
|
11
|
-
channel = Thread.createBroadcastChannel('server');
|
|
12
|
-
Exit.registerAsyncTask({
|
|
13
|
-
name: 'server',
|
|
14
|
-
stop: () => server.stop()
|
|
15
|
-
});
|
|
16
|
-
logger.debug({ source: 'server', message: 'Starting server' });
|
|
17
|
-
serverWorker = Thread.createWorker('modes/test/server', toPlainObject(configuration));
|
|
18
|
-
const { promise, resolve, reject } = Promise.withResolvers();
|
|
19
|
-
channel.onmessage = ({ data: message }) => {
|
|
20
|
-
if (message.command === 'ready') {
|
|
21
|
-
resolve(message.port);
|
|
22
|
-
}
|
|
23
|
-
else if (message.command === 'error') {
|
|
24
|
-
reject(new Error('failed to start'));
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
assert(false, 'unexpected');
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
return promise;
|
|
31
|
-
},
|
|
32
|
-
async stop() {
|
|
33
|
-
if (stopping) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
stopping = true;
|
|
37
|
-
try {
|
|
38
|
-
assert(serverWorker !== undefined);
|
|
39
|
-
logger.debug({ source: 'server', message: 'Stopping server' });
|
|
40
|
-
channel.postMessage({
|
|
41
|
-
command: 'terminate'
|
|
42
|
-
});
|
|
43
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
44
|
-
channel.onmessage = ({ data: message }) => {
|
|
45
|
-
if (message.command === 'terminated') {
|
|
46
|
-
channel.close();
|
|
47
|
-
resolve();
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
await promise;
|
|
51
|
-
}
|
|
52
|
-
finally {
|
|
53
|
-
channel.close();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
export const workerMain = (configuration) => {
|
|
58
|
-
logger.debug({ source: 'server', message: 'Starting server...' });
|
|
59
|
-
channel = Thread.createBroadcastChannel('server');
|
|
60
|
-
channel.onmessage = ({ data: message }) => {
|
|
61
|
-
if (message.command === 'terminate') {
|
|
62
|
-
logger.debug({ source: 'server', message: 'Stopping server...' });
|
|
63
|
-
void server.close().finally(() => {
|
|
64
|
-
logger.debug({ source: 'server', message: 'Server stopped.' });
|
|
65
|
-
channel.postMessage({
|
|
66
|
-
command: 'terminated'
|
|
67
|
-
});
|
|
68
|
-
channel.close();
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
let server;
|
|
73
|
-
try {
|
|
74
|
-
server = serve(buildREserveConfiguration(configuration));
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
logger.error({ source: 'server', message: 'An error occurred while configuring', error });
|
|
78
|
-
channel.postMessage({
|
|
79
|
-
command: 'error'
|
|
80
|
-
});
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
server.on('created', () => {
|
|
84
|
-
logger.debug({ source: 'reserve', message: 'created', data: {} });
|
|
85
|
-
});
|
|
86
|
-
for (const eventName of ['incoming', 'redirecting', 'redirected', 'aborted', 'closed']) {
|
|
87
|
-
server.on(eventName, (event) => {
|
|
88
|
-
const { eventName: message, ...data } = event;
|
|
89
|
-
logger.debug({ source: 'reserve', message, data });
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
server
|
|
93
|
-
.on('ready', (event) => {
|
|
94
|
-
const { eventName: message, ...data } = event;
|
|
95
|
-
logger.debug({ source: 'reserve', message, data });
|
|
96
|
-
logger.info({ source: 'server', message: `Server listening on: ${event.url}` });
|
|
97
|
-
channel.postMessage({
|
|
98
|
-
command: 'ready',
|
|
99
|
-
port: event.port
|
|
100
|
-
});
|
|
101
|
-
})
|
|
102
|
-
.on('error', (event) => {
|
|
103
|
-
const { eventName: message, reason: error, ...data } = event;
|
|
104
|
-
logger.debug({ source: 'reserve', message, data, error });
|
|
105
|
-
channel.postMessage({
|
|
106
|
-
command: 'error'
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
};
|