rapporDeTest.html 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8"/>
  5. <title id="head-title">rapporDeTest.html</title>
  6. <style type="text/css">body {
  7. font-family: Helvetica, Arial, sans-serif;
  8. font-size: 12px;
  9. /* do not increase min-width as some may use split screens */
  10. min-width: 800px;
  11. color: #999;
  12. }
  13. h1 {
  14. font-size: 24px;
  15. color: black;
  16. }
  17. h2 {
  18. font-size: 16px;
  19. color: black;
  20. }
  21. p {
  22. color: black;
  23. }
  24. a {
  25. color: #999;
  26. }
  27. table {
  28. border-collapse: collapse;
  29. }
  30. /******************************
  31. * SUMMARY INFORMATION
  32. ******************************/
  33. #environment td {
  34. padding: 5px;
  35. border: 1px solid #e6e6e6;
  36. vertical-align: top;
  37. }
  38. #environment tr:nth-child(odd) {
  39. background-color: #f6f6f6;
  40. }
  41. #environment ul {
  42. margin: 0;
  43. padding: 0 20px;
  44. }
  45. /******************************
  46. * TEST RESULT COLORS
  47. ******************************/
  48. span.passed,
  49. .passed .col-result {
  50. color: green;
  51. }
  52. span.skipped,
  53. span.xfailed,
  54. span.rerun,
  55. .skipped .col-result,
  56. .xfailed .col-result,
  57. .rerun .col-result {
  58. color: orange;
  59. }
  60. span.error,
  61. span.failed,
  62. span.xpassed,
  63. .error .col-result,
  64. .failed .col-result,
  65. .xpassed .col-result {
  66. color: red;
  67. }
  68. .col-links__extra {
  69. margin-right: 3px;
  70. }
  71. /******************************
  72. * RESULTS TABLE
  73. *
  74. * 1. Table Layout
  75. * 2. Extra
  76. * 3. Sorting items
  77. *
  78. ******************************/
  79. /*------------------
  80. * 1. Table Layout
  81. *------------------*/
  82. #results-table {
  83. border: 1px solid #e6e6e6;
  84. color: #999;
  85. font-size: 12px;
  86. width: 100%;
  87. }
  88. #results-table th,
  89. #results-table td {
  90. padding: 5px;
  91. border: 1px solid #e6e6e6;
  92. text-align: left;
  93. }
  94. #results-table th {
  95. font-weight: bold;
  96. }
  97. /*------------------
  98. * 2. Extra
  99. *------------------*/
  100. .logwrapper {
  101. max-height: 230px;
  102. overflow-y: scroll;
  103. background-color: #e6e6e6;
  104. }
  105. .logwrapper.expanded {
  106. max-height: none;
  107. }
  108. .logwrapper.expanded .logexpander:after {
  109. content: "collapse [-]";
  110. }
  111. .logwrapper .logexpander {
  112. z-index: 1;
  113. position: sticky;
  114. top: 10px;
  115. width: max-content;
  116. border: 1px solid;
  117. border-radius: 3px;
  118. padding: 5px 7px;
  119. margin: 10px 0 10px calc(100% - 80px);
  120. cursor: pointer;
  121. background-color: #e6e6e6;
  122. }
  123. .logwrapper .logexpander:after {
  124. content: "expand [+]";
  125. }
  126. .logwrapper .logexpander:hover {
  127. color: #000;
  128. border-color: #000;
  129. }
  130. .logwrapper .log {
  131. min-height: 40px;
  132. position: relative;
  133. top: -50px;
  134. height: calc(100% + 50px);
  135. border: 1px solid #e6e6e6;
  136. color: black;
  137. display: block;
  138. font-family: "Courier New", Courier, monospace;
  139. padding: 5px;
  140. padding-right: 80px;
  141. white-space: pre-wrap;
  142. }
  143. div.media {
  144. border: 1px solid #e6e6e6;
  145. float: right;
  146. height: 240px;
  147. margin: 0 5px;
  148. overflow: hidden;
  149. width: 320px;
  150. }
  151. .media-container {
  152. display: grid;
  153. grid-template-columns: 25px auto 25px;
  154. align-items: center;
  155. flex: 1 1;
  156. overflow: hidden;
  157. height: 200px;
  158. }
  159. .media-container--fullscreen {
  160. grid-template-columns: 0px auto 0px;
  161. }
  162. .media-container__nav--right,
  163. .media-container__nav--left {
  164. text-align: center;
  165. cursor: pointer;
  166. }
  167. .media-container__viewport {
  168. cursor: pointer;
  169. text-align: center;
  170. height: inherit;
  171. }
  172. .media-container__viewport img,
  173. .media-container__viewport video {
  174. object-fit: cover;
  175. width: 100%;
  176. max-height: 100%;
  177. }
  178. .media__name,
  179. .media__counter {
  180. display: flex;
  181. flex-direction: row;
  182. justify-content: space-around;
  183. flex: 0 0 25px;
  184. align-items: center;
  185. }
  186. .collapsible td:not(.col-links) {
  187. cursor: pointer;
  188. }
  189. .collapsible td:not(.col-links):hover::after {
  190. color: #bbb;
  191. font-style: italic;
  192. cursor: pointer;
  193. }
  194. .col-result {
  195. width: 130px;
  196. }
  197. .col-result:hover::after {
  198. content: " (hide details)";
  199. }
  200. .col-result.collapsed:hover::after {
  201. content: " (show details)";
  202. }
  203. #environment-header h2:hover::after {
  204. content: " (hide details)";
  205. color: #bbb;
  206. font-style: italic;
  207. cursor: pointer;
  208. font-size: 12px;
  209. }
  210. #environment-header.collapsed h2:hover::after {
  211. content: " (show details)";
  212. color: #bbb;
  213. font-style: italic;
  214. cursor: pointer;
  215. font-size: 12px;
  216. }
  217. /*------------------
  218. * 3. Sorting items
  219. *------------------*/
  220. .sortable {
  221. cursor: pointer;
  222. }
  223. .sortable.desc:after {
  224. content: " ";
  225. position: relative;
  226. left: 5px;
  227. bottom: -12.5px;
  228. border: 10px solid #4caf50;
  229. border-bottom: 0;
  230. border-left-color: transparent;
  231. border-right-color: transparent;
  232. }
  233. .sortable.asc:after {
  234. content: " ";
  235. position: relative;
  236. left: 5px;
  237. bottom: 12.5px;
  238. border: 10px solid #4caf50;
  239. border-top: 0;
  240. border-left-color: transparent;
  241. border-right-color: transparent;
  242. }
  243. .hidden, .summary__reload__button.hidden {
  244. display: none;
  245. }
  246. .summary__data {
  247. flex: 0 0 550px;
  248. }
  249. .summary__reload {
  250. flex: 1 1;
  251. display: flex;
  252. justify-content: center;
  253. }
  254. .summary__reload__button {
  255. flex: 0 0 300px;
  256. display: flex;
  257. color: white;
  258. font-weight: bold;
  259. background-color: #4caf50;
  260. text-align: center;
  261. justify-content: center;
  262. align-items: center;
  263. border-radius: 3px;
  264. cursor: pointer;
  265. }
  266. .summary__reload__button:hover {
  267. background-color: #46a049;
  268. }
  269. .summary__spacer {
  270. flex: 0 0 550px;
  271. }
  272. .controls {
  273. display: flex;
  274. justify-content: space-between;
  275. }
  276. .filters,
  277. .collapse {
  278. display: flex;
  279. align-items: center;
  280. }
  281. .filters button,
  282. .collapse button {
  283. color: #999;
  284. border: none;
  285. background: none;
  286. cursor: pointer;
  287. text-decoration: underline;
  288. }
  289. .filters button:hover,
  290. .collapse button:hover {
  291. color: #ccc;
  292. }
  293. .filter__label {
  294. margin-right: 10px;
  295. }
  296. </style>
  297. </head>
  298. <body>
  299. <h1 id="title">rapporDeTest.html</h1>
  300. <p>Report generated on 11-Jan-2024 at 14:35:43 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a>
  301. v4.1.1</p>
  302. <div id="environment-header">
  303. <h2>Environment</h2>
  304. </div>
  305. <table id="environment"></table>
  306. <!-- TEMPLATES -->
  307. <template id="template_environment_row">
  308. <tr>
  309. <td></td>
  310. <td></td>
  311. </tr>
  312. </template>
  313. <template id="template_results-table__body--empty">
  314. <tbody class="results-table-row">
  315. <tr id="not-found-message">
  316. <td colspan="4">No results found. Check the filters.</th>
  317. </tr>
  318. </template>
  319. <template id="template_results-table__tbody">
  320. <tbody class="results-table-row">
  321. <tr class="collapsible">
  322. </tr>
  323. <tr class="extras-row">
  324. <td class="extra" colspan="4">
  325. <div class="extraHTML"></div>
  326. <div class="media">
  327. <div class="media-container">
  328. <div class="media-container__nav--left"><</div>
  329. <div class="media-container__viewport">
  330. <img src="" />
  331. <video controls>
  332. <source src="" type="video/mp4">
  333. </video>
  334. </div>
  335. <div class="media-container__nav--right">></div>
  336. </div>
  337. <div class="media__name"></div>
  338. <div class="media__counter"></div>
  339. </div>
  340. <div class="logwrapper">
  341. <div class="logexpander"></div>
  342. <div class="log"></div>
  343. </div>
  344. </td>
  345. </tr>
  346. </tbody>
  347. </template>
  348. <!-- END TEMPLATES -->
  349. <div class="summary">
  350. <div class="summary__data">
  351. <h2>Summary</h2>
  352. <div class="additional-summary prefix">
  353. </div>
  354. <p class="run-count">1 test took 00:00:16.</p>
  355. <p class="filter">(Un)check the boxes to filter the results.</p>
  356. <div class="summary__reload">
  357. <div class="summary__reload__button hidden" onclick="location.reload()">
  358. <div>There are still tests running. <br />Reload this page to get the latest results!</div>
  359. </div>
  360. </div>
  361. <div class="summary__spacer"></div>
  362. <div class="controls">
  363. <div class="filters">
  364. <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="failed" disabled/>
  365. <span class="failed">0 Failed,</span>
  366. <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="passed" />
  367. <span class="passed">1 Passed,</span>
  368. <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="skipped" />
  369. <span class="skipped">1 Skipped,</span>
  370. <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="xfailed" disabled/>
  371. <span class="xfailed">0 Expected failures,</span>
  372. <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="xpassed" disabled/>
  373. <span class="xpassed">0 Unexpected passes,</span>
  374. <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="error" disabled/>
  375. <span class="error">0 Errors,</span>
  376. <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="rerun" disabled/>
  377. <span class="rerun">0 Reruns</span>
  378. </div>
  379. <div class="collapse">
  380. <button id="show_all_details">Show all details</button>&nbsp;/&nbsp;<button id="hide_all_details">Hide all details</button>
  381. </div>
  382. </div>
  383. </div>
  384. <div class="additional-summary summary">
  385. </div>
  386. <div class="additional-summary postfix">
  387. </div>
  388. </div>
  389. <table id="results-table">
  390. <thead id="results-table-head">
  391. <tr>
  392. <th class="sortable" data-column-type="result">Result</th>
  393. <th class="sortable" data-column-type="testId">Test</th>
  394. <th class="sortable" data-column-type="duration">Duration</th>
  395. <th>Links</th>
  396. </tr>
  397. </thead>
  398. </table>
  399. </body>
  400. <footer>
  401. <div id="data-container" data-jsonblob="{&#34;environment&#34;: {&#34;Python&#34;: &#34;3.12.1&#34;, &#34;Platform&#34;: &#34;Windows-10-10.0.19045-SP0&#34;, &#34;Packages&#34;: {&#34;pytest&#34;: &#34;7.4.4&#34;, &#34;pluggy&#34;: &#34;1.3.0&#34;}, &#34;Plugins&#34;: {&#34;html&#34;: &#34;4.1.1&#34;, &#34;metadata&#34;: &#34;3.0.0&#34;}}, &#34;tests&#34;: {&#34;test_main.py::test_connexion_saucedemo&#34;: [{&#34;extras&#34;: [], &#34;result&#34;: &#34;Passed&#34;, &#34;testId&#34;: &#34;test_main.py::test_connexion_saucedemo&#34;, &#34;duration&#34;: &#34;00:00:16&#34;, &#34;resultsTableRow&#34;: [&#34;&lt;td class=\&#34;col-result\&#34;&gt;Passed&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-testId\&#34;&gt;test_main.py::test_connexion_saucedemo&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-duration\&#34;&gt;00:00:16&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-links\&#34;&gt;&lt;/td&gt;&#34;], &#34;log&#34;: &#34;------------------------------ Captured log call -------------------------------\nINFO root:test_main.py:42 Test du Login avec succ\u00e8s\nINFO root:test_main.py:50 le titre de la page : Swag Labs\nINFO root:test_main.py:51 L&amp;#x27;URL de la page : https://www.saucedemo.com/\n\n&#34;}], &#34;test_saucedemo.py::test_loginOK&#34;: [{&#34;extras&#34;: [], &#34;result&#34;: &#34;Skipped&#34;, &#34;testId&#34;: &#34;test_saucedemo.py::test_loginOK::setup&#34;, &#34;duration&#34;: &#34;1 ms&#34;, &#34;resultsTableRow&#34;: [&#34;&lt;td class=\&#34;col-result\&#34;&gt;Skipped&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-testId\&#34;&gt;test_saucedemo.py::test_loginOK::setup&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-duration\&#34;&gt;1 ms&lt;/td&gt;&#34;, &#34;&lt;td class=\&#34;col-links\&#34;&gt;&lt;/td&gt;&#34;], &#34;log&#34;: &#34;(&amp;#x27;C:\\\\Users\\\\Administrateur\\\\Documents\\\\Formation testeur\\\\devops_selenium-TPj2\\\\test_saucedemo.py&amp;#x27;, 16, &amp;#x27;Skipped: Je veux pas le faire&amp;#x27;)\n&#34;}]}, &#34;renderCollapsed&#34;: [&#34;passed&#34;], &#34;initialSort&#34;: &#34;result&#34;, &#34;title&#34;: &#34;rapporDeTest.html&#34;}"></div>
  402. <script>
  403. (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
  404. const { getCollapsedCategory, setCollapsedIds } = require('./storage.js')
  405. class DataManager {
  406. setManager(data) {
  407. const collapsedCategories = [...getCollapsedCategory(data.renderCollapsed)]
  408. const collapsedIds = []
  409. const tests = Object.values(data.tests).flat().map((test, index) => {
  410. const collapsed = collapsedCategories.includes(test.result.toLowerCase())
  411. const id = `test_${index}`
  412. if (collapsed) {
  413. collapsedIds.push(id)
  414. }
  415. return {
  416. ...test,
  417. id,
  418. collapsed,
  419. }
  420. })
  421. const dataBlob = { ...data, tests }
  422. this.data = { ...dataBlob }
  423. this.renderData = { ...dataBlob }
  424. setCollapsedIds(collapsedIds)
  425. }
  426. get allData() {
  427. return { ...this.data }
  428. }
  429. resetRender() {
  430. this.renderData = { ...this.data }
  431. }
  432. setRender(data) {
  433. this.renderData.tests = [...data]
  434. }
  435. toggleCollapsedItem(id) {
  436. this.renderData.tests = this.renderData.tests.map((test) =>
  437. test.id === id ? { ...test, collapsed: !test.collapsed } : test,
  438. )
  439. }
  440. set allCollapsed(collapsed) {
  441. this.renderData = { ...this.renderData, tests: [...this.renderData.tests.map((test) => (
  442. { ...test, collapsed }
  443. ))] }
  444. }
  445. get testSubset() {
  446. return [...this.renderData.tests]
  447. }
  448. get environment() {
  449. return this.renderData.environment
  450. }
  451. get initialSort() {
  452. return this.data.initialSort
  453. }
  454. }
  455. module.exports = {
  456. manager: new DataManager(),
  457. }
  458. },{"./storage.js":8}],2:[function(require,module,exports){
  459. const mediaViewer = require('./mediaviewer.js')
  460. const templateEnvRow = document.getElementById('template_environment_row')
  461. const templateResult = document.getElementById('template_results-table__tbody')
  462. function htmlToElements(html) {
  463. const temp = document.createElement('template')
  464. temp.innerHTML = html
  465. return temp.content.childNodes
  466. }
  467. const find = (selector, elem) => {
  468. if (!elem) {
  469. elem = document
  470. }
  471. return elem.querySelector(selector)
  472. }
  473. const findAll = (selector, elem) => {
  474. if (!elem) {
  475. elem = document
  476. }
  477. return [...elem.querySelectorAll(selector)]
  478. }
  479. const dom = {
  480. getStaticRow: (key, value) => {
  481. const envRow = templateEnvRow.content.cloneNode(true)
  482. const isObj = typeof value === 'object' && value !== null
  483. const values = isObj ? Object.keys(value).map((k) => `${k}: ${value[k]}`) : null
  484. const valuesElement = htmlToElements(
  485. values ? `<ul>${values.map((val) => `<li>${val}</li>`).join('')}<ul>` : `<div>${value}</div>`)[0]
  486. const td = findAll('td', envRow)
  487. td[0].textContent = key
  488. td[1].appendChild(valuesElement)
  489. return envRow
  490. },
  491. getResultTBody: ({ testId, id, log, extras, resultsTableRow, tableHtml, result, collapsed }) => {
  492. const resultBody = templateResult.content.cloneNode(true)
  493. resultBody.querySelector('tbody').classList.add(result.toLowerCase())
  494. resultBody.querySelector('tbody').id = testId
  495. resultBody.querySelector('.collapsible').dataset.id = id
  496. resultsTableRow.forEach((html) => {
  497. const t = document.createElement('template')
  498. t.innerHTML = html
  499. resultBody.querySelector('.collapsible').appendChild(t.content)
  500. })
  501. if (log) {
  502. // Wrap lines starting with "E" with span.error to color those lines red
  503. const wrappedLog = log.replace(/^E.*$/gm, (match) => `<span class="error">${match}</span>`)
  504. resultBody.querySelector('.log').innerHTML = wrappedLog
  505. } else {
  506. resultBody.querySelector('.log').remove()
  507. }
  508. if (collapsed) {
  509. resultBody.querySelector('.collapsible > td')?.classList.add('collapsed')
  510. resultBody.querySelector('.extras-row').classList.add('hidden')
  511. } else {
  512. resultBody.querySelector('.collapsible > td')?.classList.remove('collapsed')
  513. }
  514. const media = []
  515. extras?.forEach(({ name, format_type, content }) => {
  516. if (['image', 'video'].includes(format_type)) {
  517. media.push({ path: content, name, format_type })
  518. }
  519. if (format_type === 'html') {
  520. resultBody.querySelector('.extraHTML').insertAdjacentHTML('beforeend', `<div>${content}</div>`)
  521. }
  522. })
  523. mediaViewer.setup(resultBody, media)
  524. // Add custom html from the pytest_html_results_table_html hook
  525. tableHtml?.forEach((item) => {
  526. resultBody.querySelector('td[class="extra"]').insertAdjacentHTML('beforeend', item)
  527. })
  528. return resultBody
  529. },
  530. }
  531. module.exports = {
  532. dom,
  533. htmlToElements,
  534. find,
  535. findAll,
  536. }
  537. },{"./mediaviewer.js":6}],3:[function(require,module,exports){
  538. const { manager } = require('./datamanager.js')
  539. const { doSort } = require('./sort.js')
  540. const storageModule = require('./storage.js')
  541. const getFilteredSubSet = (filter) =>
  542. manager.allData.tests.filter(({ result }) => filter.includes(result.toLowerCase()))
  543. const doInitFilter = () => {
  544. const currentFilter = storageModule.getVisible()
  545. const filteredSubset = getFilteredSubSet(currentFilter)
  546. manager.setRender(filteredSubset)
  547. }
  548. const doFilter = (type, show) => {
  549. if (show) {
  550. storageModule.showCategory(type)
  551. } else {
  552. storageModule.hideCategory(type)
  553. }
  554. const currentFilter = storageModule.getVisible()
  555. const filteredSubset = getFilteredSubSet(currentFilter)
  556. manager.setRender(filteredSubset)
  557. const sortColumn = storageModule.getSort()
  558. doSort(sortColumn, true)
  559. }
  560. module.exports = {
  561. doFilter,
  562. doInitFilter,
  563. }
  564. },{"./datamanager.js":1,"./sort.js":7,"./storage.js":8}],4:[function(require,module,exports){
  565. const { redraw, bindEvents, renderStatic } = require('./main.js')
  566. const { doInitFilter } = require('./filter.js')
  567. const { doInitSort } = require('./sort.js')
  568. const { manager } = require('./datamanager.js')
  569. const data = JSON.parse(document.getElementById('data-container').dataset.jsonblob)
  570. function init() {
  571. manager.setManager(data)
  572. doInitFilter()
  573. doInitSort()
  574. renderStatic()
  575. redraw()
  576. bindEvents()
  577. }
  578. init()
  579. },{"./datamanager.js":1,"./filter.js":3,"./main.js":5,"./sort.js":7}],5:[function(require,module,exports){
  580. const { dom, find, findAll } = require('./dom.js')
  581. const { manager } = require('./datamanager.js')
  582. const { doSort } = require('./sort.js')
  583. const { doFilter } = require('./filter.js')
  584. const {
  585. getVisible,
  586. getCollapsedIds,
  587. setCollapsedIds,
  588. getSort,
  589. getSortDirection,
  590. possibleFilters,
  591. } = require('./storage.js')
  592. const removeChildren = (node) => {
  593. while (node.firstChild) {
  594. node.removeChild(node.firstChild)
  595. }
  596. }
  597. const renderStatic = () => {
  598. const renderEnvironmentTable = () => {
  599. const environment = manager.environment
  600. const rows = Object.keys(environment).map((key) => dom.getStaticRow(key, environment[key]))
  601. const table = document.getElementById('environment')
  602. removeChildren(table)
  603. rows.forEach((row) => table.appendChild(row))
  604. }
  605. renderEnvironmentTable()
  606. }
  607. const addItemToggleListener = (elem) => {
  608. elem.addEventListener('click', ({ target }) => {
  609. const id = target.parentElement.dataset.id
  610. manager.toggleCollapsedItem(id)
  611. const collapsedIds = getCollapsedIds()
  612. if (collapsedIds.includes(id)) {
  613. const updated = collapsedIds.filter((item) => item !== id)
  614. setCollapsedIds(updated)
  615. } else {
  616. collapsedIds.push(id)
  617. setCollapsedIds(collapsedIds)
  618. }
  619. redraw()
  620. })
  621. }
  622. const renderContent = (tests) => {
  623. const sortAttr = getSort(manager.initialSort)
  624. const sortAsc = JSON.parse(getSortDirection())
  625. const rows = tests.map(dom.getResultTBody)
  626. const table = document.getElementById('results-table')
  627. const tableHeader = document.getElementById('results-table-head')
  628. const newTable = document.createElement('table')
  629. newTable.id = 'results-table'
  630. // remove all sorting classes and set the relevant
  631. findAll('.sortable', tableHeader).forEach((elem) => elem.classList.remove('asc', 'desc'))
  632. tableHeader.querySelector(`.sortable[data-column-type="${sortAttr}"]`)?.classList.add(sortAsc ? 'desc' : 'asc')
  633. newTable.appendChild(tableHeader)
  634. if (!rows.length) {
  635. const emptyTable = document.getElementById('template_results-table__body--empty').content.cloneNode(true)
  636. newTable.appendChild(emptyTable)
  637. } else {
  638. rows.forEach((row) => {
  639. if (!!row) {
  640. findAll('.collapsible td:not(.col-links', row).forEach(addItemToggleListener)
  641. find('.logexpander', row).addEventListener('click',
  642. (evt) => evt.target.parentNode.classList.toggle('expanded'),
  643. )
  644. newTable.appendChild(row)
  645. }
  646. })
  647. }
  648. table.replaceWith(newTable)
  649. }
  650. const renderDerived = () => {
  651. const currentFilter = getVisible()
  652. possibleFilters.forEach((result) => {
  653. const input = document.querySelector(`input[data-test-result="${result}"]`)
  654. input.checked = currentFilter.includes(result)
  655. })
  656. }
  657. const bindEvents = () => {
  658. const filterColumn = (evt) => {
  659. const { target: element } = evt
  660. const { testResult } = element.dataset
  661. doFilter(testResult, element.checked)
  662. const collapsedIds = getCollapsedIds()
  663. const updated = manager.renderData.tests.map((test) => {
  664. return {
  665. ...test,
  666. collapsed: collapsedIds.includes(test.id),
  667. }
  668. })
  669. manager.setRender(updated)
  670. redraw()
  671. }
  672. const header = document.getElementById('environment-header')
  673. header.addEventListener('click', () => {
  674. const table = document.getElementById('environment')
  675. table.classList.toggle('hidden')
  676. header.classList.toggle('collapsed')
  677. })
  678. findAll('input[name="filter_checkbox"]').forEach((elem) => {
  679. elem.addEventListener('click', filterColumn)
  680. })
  681. findAll('.sortable').forEach((elem) => {
  682. elem.addEventListener('click', (evt) => {
  683. const { target: element } = evt
  684. const { columnType } = element.dataset
  685. doSort(columnType)
  686. redraw()
  687. })
  688. })
  689. document.getElementById('show_all_details').addEventListener('click', () => {
  690. manager.allCollapsed = false
  691. setCollapsedIds([])
  692. redraw()
  693. })
  694. document.getElementById('hide_all_details').addEventListener('click', () => {
  695. manager.allCollapsed = true
  696. const allIds = manager.renderData.tests.map((test) => test.id)
  697. setCollapsedIds(allIds)
  698. redraw()
  699. })
  700. }
  701. const redraw = () => {
  702. const { testSubset } = manager
  703. renderContent(testSubset)
  704. renderDerived()
  705. }
  706. module.exports = {
  707. redraw,
  708. bindEvents,
  709. renderStatic,
  710. }
  711. },{"./datamanager.js":1,"./dom.js":2,"./filter.js":3,"./sort.js":7,"./storage.js":8}],6:[function(require,module,exports){
  712. class MediaViewer {
  713. constructor(assets) {
  714. this.assets = assets
  715. this.index = 0
  716. }
  717. nextActive() {
  718. this.index = this.index === this.assets.length - 1 ? 0 : this.index + 1
  719. return [this.activeFile, this.index]
  720. }
  721. prevActive() {
  722. this.index = this.index === 0 ? this.assets.length - 1 : this.index -1
  723. return [this.activeFile, this.index]
  724. }
  725. get currentIndex() {
  726. return this.index
  727. }
  728. get activeFile() {
  729. return this.assets[this.index]
  730. }
  731. }
  732. const setup = (resultBody, assets) => {
  733. if (!assets.length) {
  734. resultBody.querySelector('.media').classList.add('hidden')
  735. return
  736. }
  737. const mediaViewer = new MediaViewer(assets)
  738. const container = resultBody.querySelector('.media-container')
  739. const leftArrow = resultBody.querySelector('.media-container__nav--left')
  740. const rightArrow = resultBody.querySelector('.media-container__nav--right')
  741. const mediaName = resultBody.querySelector('.media__name')
  742. const counter = resultBody.querySelector('.media__counter')
  743. const imageEl = resultBody.querySelector('img')
  744. const sourceEl = resultBody.querySelector('source')
  745. const videoEl = resultBody.querySelector('video')
  746. const setImg = (media, index) => {
  747. if (media?.format_type === 'image') {
  748. imageEl.src = media.path
  749. imageEl.classList.remove('hidden')
  750. videoEl.classList.add('hidden')
  751. } else if (media?.format_type === 'video') {
  752. sourceEl.src = media.path
  753. videoEl.classList.remove('hidden')
  754. imageEl.classList.add('hidden')
  755. }
  756. mediaName.innerText = media?.name
  757. counter.innerText = `${index + 1} / ${assets.length}`
  758. }
  759. setImg(mediaViewer.activeFile, mediaViewer.currentIndex)
  760. const moveLeft = () => {
  761. const [media, index] = mediaViewer.prevActive()
  762. setImg(media, index)
  763. }
  764. const doRight = () => {
  765. const [media, index] = mediaViewer.nextActive()
  766. setImg(media, index)
  767. }
  768. const openImg = () => {
  769. window.open(mediaViewer.activeFile.path, '_blank')
  770. }
  771. if (assets.length === 1) {
  772. container.classList.add('media-container--fullscreen')
  773. } else {
  774. leftArrow.addEventListener('click', moveLeft)
  775. rightArrow.addEventListener('click', doRight)
  776. }
  777. imageEl.addEventListener('click', openImg)
  778. }
  779. module.exports = {
  780. setup,
  781. }
  782. },{}],7:[function(require,module,exports){
  783. const { manager } = require('./datamanager.js')
  784. const storageModule = require('./storage.js')
  785. const genericSort = (list, key, ascending, customOrder) => {
  786. let sorted
  787. if (customOrder) {
  788. sorted = list.sort((a, b) => {
  789. const aValue = a.result.toLowerCase()
  790. const bValue = b.result.toLowerCase()
  791. const aIndex = customOrder.findIndex((item) => item.toLowerCase() === aValue)
  792. const bIndex = customOrder.findIndex((item) => item.toLowerCase() === bValue)
  793. // Compare the indices to determine the sort order
  794. return aIndex - bIndex
  795. })
  796. } else {
  797. sorted = list.sort((a, b) => a[key] === b[key] ? 0 : a[key] > b[key] ? 1 : -1)
  798. }
  799. if (ascending) {
  800. sorted.reverse()
  801. }
  802. return sorted
  803. }
  804. const durationSort = (list, ascending) => {
  805. const parseDuration = (duration) => {
  806. if (duration.includes(':')) {
  807. // If it's in the format "HH:mm:ss"
  808. const [hours, minutes, seconds] = duration.split(':').map(Number)
  809. return (hours * 3600 + minutes * 60 + seconds) * 1000
  810. } else {
  811. // If it's in the format "nnn ms"
  812. return parseInt(duration)
  813. }
  814. }
  815. const sorted = list.sort((a, b) => parseDuration(a['duration']) - parseDuration(b['duration']))
  816. if (ascending) {
  817. sorted.reverse()
  818. }
  819. return sorted
  820. }
  821. const doInitSort = () => {
  822. const type = storageModule.getSort(manager.initialSort)
  823. const ascending = storageModule.getSortDirection()
  824. const list = manager.testSubset
  825. const initialOrder = ['Error', 'Failed', 'Rerun', 'XFailed', 'XPassed', 'Skipped', 'Passed']
  826. storageModule.setSort(type)
  827. storageModule.setSortDirection(ascending)
  828. if (type?.toLowerCase() === 'original') {
  829. manager.setRender(list)
  830. } else {
  831. let sortedList
  832. switch (type) {
  833. case 'duration':
  834. sortedList = durationSort(list, ascending)
  835. break
  836. case 'result':
  837. sortedList = genericSort(list, type, ascending, initialOrder)
  838. break
  839. default:
  840. sortedList = genericSort(list, type, ascending)
  841. break
  842. }
  843. manager.setRender(sortedList)
  844. }
  845. }
  846. const doSort = (type, skipDirection) => {
  847. const newSortType = storageModule.getSort(manager.initialSort) !== type
  848. const currentAsc = storageModule.getSortDirection()
  849. let ascending
  850. if (skipDirection) {
  851. ascending = currentAsc
  852. } else {
  853. ascending = newSortType ? false : !currentAsc
  854. }
  855. storageModule.setSort(type)
  856. storageModule.setSortDirection(ascending)
  857. const list = manager.testSubset
  858. const sortedList = type === 'duration' ? durationSort(list, ascending) : genericSort(list, type, ascending)
  859. manager.setRender(sortedList)
  860. }
  861. module.exports = {
  862. doInitSort,
  863. doSort,
  864. }
  865. },{"./datamanager.js":1,"./storage.js":8}],8:[function(require,module,exports){
  866. const possibleFilters = [
  867. 'passed',
  868. 'skipped',
  869. 'failed',
  870. 'error',
  871. 'xfailed',
  872. 'xpassed',
  873. 'rerun',
  874. ]
  875. const getVisible = () => {
  876. const url = new URL(window.location.href)
  877. const settings = new URLSearchParams(url.search).get('visible')
  878. const lower = (item) => {
  879. const lowerItem = item.toLowerCase()
  880. if (possibleFilters.includes(lowerItem)) {
  881. return lowerItem
  882. }
  883. return null
  884. }
  885. return settings === null ?
  886. possibleFilters :
  887. [...new Set(settings?.split(',').map(lower).filter((item) => item))]
  888. }
  889. const hideCategory = (categoryToHide) => {
  890. const url = new URL(window.location.href)
  891. const visibleParams = new URLSearchParams(url.search).get('visible')
  892. const currentVisible = visibleParams ? visibleParams.split(',') : [...possibleFilters]
  893. const settings = [...new Set(currentVisible)].filter((f) => f !== categoryToHide).join(',')
  894. url.searchParams.set('visible', settings)
  895. window.history.pushState({}, null, unescape(url.href))
  896. }
  897. const showCategory = (categoryToShow) => {
  898. if (typeof window === 'undefined') {
  899. return
  900. }
  901. const url = new URL(window.location.href)
  902. const currentVisible = new URLSearchParams(url.search).get('visible')?.split(',').filter(Boolean) ||
  903. [...possibleFilters]
  904. const settings = [...new Set([categoryToShow, ...currentVisible])]
  905. const noFilter = possibleFilters.length === settings.length || !settings.length
  906. noFilter ? url.searchParams.delete('visible') : url.searchParams.set('visible', settings.join(','))
  907. window.history.pushState({}, null, unescape(url.href))
  908. }
  909. const getSort = (initialSort) => {
  910. const url = new URL(window.location.href)
  911. let sort = new URLSearchParams(url.search).get('sort')
  912. if (!sort) {
  913. sort = initialSort || 'result'
  914. }
  915. return sort
  916. }
  917. const setSort = (type) => {
  918. const url = new URL(window.location.href)
  919. url.searchParams.set('sort', type)
  920. window.history.pushState({}, null, unescape(url.href))
  921. }
  922. const getCollapsedCategory = (renderCollapsed) => {
  923. let categories
  924. if (typeof window !== 'undefined') {
  925. const url = new URL(window.location.href)
  926. const collapsedItems = new URLSearchParams(url.search).get('collapsed')
  927. switch (true) {
  928. case !renderCollapsed && collapsedItems === null:
  929. categories = ['passed']
  930. break
  931. case collapsedItems?.length === 0 || /^["']{2}$/.test(collapsedItems):
  932. categories = []
  933. break
  934. case /^all$/.test(collapsedItems) || collapsedItems === null && /^all$/.test(renderCollapsed):
  935. categories = [...possibleFilters]
  936. break
  937. default:
  938. categories = collapsedItems?.split(',').map((item) => item.toLowerCase()) || renderCollapsed
  939. break
  940. }
  941. } else {
  942. categories = []
  943. }
  944. return categories
  945. }
  946. const getSortDirection = () => JSON.parse(sessionStorage.getItem('sortAsc')) || false
  947. const setSortDirection = (ascending) => sessionStorage.setItem('sortAsc', ascending)
  948. const getCollapsedIds = () => JSON.parse(sessionStorage.getItem('collapsedIds')) || []
  949. const setCollapsedIds = (list) => sessionStorage.setItem('collapsedIds', JSON.stringify(list))
  950. module.exports = {
  951. getVisible,
  952. hideCategory,
  953. showCategory,
  954. getCollapsedIds,
  955. setCollapsedIds,
  956. getSort,
  957. setSort,
  958. getSortDirection,
  959. setSortDirection,
  960. getCollapsedCategory,
  961. possibleFilters,
  962. }
  963. },{}]},{},[4]);
  964. </script>
  965. </footer>
  966. </html>