DriverFreightFlowPage (APP): - 流程方法拆为单一职责的细粒度操作(open_freight_tab/fill_quantity/upload_photo_from_album/...), 流程方法仅做编排,公共 API 不变,用例零改动 - 移除全部 implicitly_wait 全局副作用,每步显式等待目标元素 - 容错点击(已知晓/确定)改为 _tap_by_text_if_present,跳过时记录 warning FreightAccountingPage (Web): - _go/_click_search 改显式等待(搜索框出现/列表数据行或空态),替代固定 2s/1s sleep - 表格等待升级为 td:visible,避开 vxe-table 固定列隐藏占位单元格 - 点击行操作按钮后显式等待弹窗出现,替代固定 800ms - 车队长下拉选择改显式等待下拉项渲染 - 可选弹窗操作统一收敛到 _try_click,跳过时记录 warning;关键确认按钮保持严格 两条 E2E 用例在真实环境回归通过(TC-D2S-001 116s / TC-D2S-002 112s)。
1094 lines
53 KiB
HTML
1094 lines
53 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<title id="head-title">report_20260902_095358.html</title>
|
|
<style type="text/css">body {
|
|
font-family: Helvetica, Arial, sans-serif;
|
|
font-size: 12px;
|
|
/* do not increase min-width as some may use split screens */
|
|
min-width: 800px;
|
|
color: #999;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 24px;
|
|
color: black;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 16px;
|
|
color: black;
|
|
}
|
|
|
|
p {
|
|
color: black;
|
|
}
|
|
|
|
a {
|
|
color: #999;
|
|
}
|
|
|
|
table {
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
/******************************
|
|
* SUMMARY INFORMATION
|
|
******************************/
|
|
#environment td {
|
|
padding: 5px;
|
|
border: 1px solid #e6e6e6;
|
|
vertical-align: top;
|
|
}
|
|
#environment tr:nth-child(odd) {
|
|
background-color: #f6f6f6;
|
|
}
|
|
#environment ul {
|
|
margin: 0;
|
|
padding: 0 20px;
|
|
}
|
|
|
|
/******************************
|
|
* TEST RESULT COLORS
|
|
******************************/
|
|
span.passed,
|
|
.passed .col-result {
|
|
color: green;
|
|
}
|
|
|
|
span.skipped,
|
|
span.xfailed,
|
|
span.rerun,
|
|
.skipped .col-result,
|
|
.xfailed .col-result,
|
|
.rerun .col-result {
|
|
color: orange;
|
|
}
|
|
|
|
span.error,
|
|
span.failed,
|
|
span.xpassed,
|
|
.error .col-result,
|
|
.failed .col-result,
|
|
.xpassed .col-result {
|
|
color: red;
|
|
}
|
|
|
|
.col-links__extra {
|
|
margin-right: 3px;
|
|
}
|
|
|
|
/******************************
|
|
* RESULTS TABLE
|
|
*
|
|
* 1. Table Layout
|
|
* 2. Extra
|
|
* 3. Sorting items
|
|
*
|
|
******************************/
|
|
/*------------------
|
|
* 1. Table Layout
|
|
*------------------*/
|
|
#results-table {
|
|
border: 1px solid #e6e6e6;
|
|
color: #999;
|
|
font-size: 12px;
|
|
width: 100%;
|
|
}
|
|
#results-table th,
|
|
#results-table td {
|
|
padding: 5px;
|
|
border: 1px solid #e6e6e6;
|
|
text-align: left;
|
|
}
|
|
#results-table th {
|
|
font-weight: bold;
|
|
}
|
|
|
|
/*------------------
|
|
* 2. Extra
|
|
*------------------*/
|
|
.logwrapper {
|
|
max-height: 230px;
|
|
overflow-y: scroll;
|
|
background-color: #e6e6e6;
|
|
}
|
|
.logwrapper.expanded {
|
|
max-height: none;
|
|
}
|
|
.logwrapper.expanded .logexpander:after {
|
|
content: "collapse [-]";
|
|
}
|
|
.logwrapper .logexpander {
|
|
z-index: 1;
|
|
position: sticky;
|
|
top: 10px;
|
|
width: max-content;
|
|
border: 1px solid;
|
|
border-radius: 3px;
|
|
padding: 5px 7px;
|
|
margin: 10px 0 10px calc(100% - 80px);
|
|
cursor: pointer;
|
|
background-color: #e6e6e6;
|
|
}
|
|
.logwrapper .logexpander:after {
|
|
content: "expand [+]";
|
|
}
|
|
.logwrapper .logexpander:hover {
|
|
color: #000;
|
|
border-color: #000;
|
|
}
|
|
.logwrapper .log {
|
|
min-height: 40px;
|
|
position: relative;
|
|
top: -50px;
|
|
height: calc(100% + 50px);
|
|
border: 1px solid #e6e6e6;
|
|
color: black;
|
|
display: block;
|
|
font-family: "Courier New", Courier, monospace;
|
|
padding: 5px;
|
|
padding-right: 80px;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
div.media {
|
|
border: 1px solid #e6e6e6;
|
|
float: right;
|
|
height: 240px;
|
|
margin: 0 5px;
|
|
overflow: hidden;
|
|
width: 320px;
|
|
}
|
|
|
|
.media-container {
|
|
display: grid;
|
|
grid-template-columns: 25px auto 25px;
|
|
align-items: center;
|
|
flex: 1 1;
|
|
overflow: hidden;
|
|
height: 200px;
|
|
}
|
|
|
|
.media-container--fullscreen {
|
|
grid-template-columns: 0px auto 0px;
|
|
}
|
|
|
|
.media-container__nav--right,
|
|
.media-container__nav--left {
|
|
text-align: center;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.media-container__viewport {
|
|
cursor: pointer;
|
|
text-align: center;
|
|
height: inherit;
|
|
}
|
|
.media-container__viewport img,
|
|
.media-container__viewport video {
|
|
object-fit: cover;
|
|
width: 100%;
|
|
max-height: 100%;
|
|
}
|
|
|
|
.media__name,
|
|
.media__counter {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-around;
|
|
flex: 0 0 25px;
|
|
align-items: center;
|
|
}
|
|
|
|
.collapsible td:not(.col-links) {
|
|
cursor: pointer;
|
|
}
|
|
.collapsible td:not(.col-links):hover::after {
|
|
color: #bbb;
|
|
font-style: italic;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.col-result {
|
|
width: 130px;
|
|
}
|
|
.col-result:hover::after {
|
|
content: " (hide details)";
|
|
}
|
|
|
|
.col-result.collapsed:hover::after {
|
|
content: " (show details)";
|
|
}
|
|
|
|
#environment-header h2:hover::after {
|
|
content: " (hide details)";
|
|
color: #bbb;
|
|
font-style: italic;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
}
|
|
|
|
#environment-header.collapsed h2:hover::after {
|
|
content: " (show details)";
|
|
color: #bbb;
|
|
font-style: italic;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
}
|
|
|
|
/*------------------
|
|
* 3. Sorting items
|
|
*------------------*/
|
|
.sortable {
|
|
cursor: pointer;
|
|
}
|
|
.sortable.desc:after {
|
|
content: " ";
|
|
position: relative;
|
|
left: 5px;
|
|
bottom: -12.5px;
|
|
border: 10px solid #4caf50;
|
|
border-bottom: 0;
|
|
border-left-color: transparent;
|
|
border-right-color: transparent;
|
|
}
|
|
.sortable.asc:after {
|
|
content: " ";
|
|
position: relative;
|
|
left: 5px;
|
|
bottom: 12.5px;
|
|
border: 10px solid #4caf50;
|
|
border-top: 0;
|
|
border-left-color: transparent;
|
|
border-right-color: transparent;
|
|
}
|
|
|
|
.hidden, .summary__reload__button.hidden {
|
|
display: none;
|
|
}
|
|
|
|
.summary__data {
|
|
flex: 0 0 550px;
|
|
}
|
|
.summary__reload {
|
|
flex: 1 1;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
.summary__reload__button {
|
|
flex: 0 0 300px;
|
|
display: flex;
|
|
color: white;
|
|
font-weight: bold;
|
|
background-color: #4caf50;
|
|
text-align: center;
|
|
justify-content: center;
|
|
align-items: center;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
}
|
|
.summary__reload__button:hover {
|
|
background-color: #46a049;
|
|
}
|
|
.summary__spacer {
|
|
flex: 0 0 550px;
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.filters,
|
|
.collapse {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
.filters button,
|
|
.collapse button {
|
|
color: #999;
|
|
border: none;
|
|
background: none;
|
|
cursor: pointer;
|
|
text-decoration: underline;
|
|
}
|
|
.filters button:hover,
|
|
.collapse button:hover {
|
|
color: #ccc;
|
|
}
|
|
|
|
.filter__label {
|
|
margin-right: 10px;
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
<body>
|
|
<h1 id="title">report_20260902_095358.html</h1>
|
|
<p>Report generated on 02-Sep-2026 at 10:00:22 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a>
|
|
v4.2.0</p>
|
|
<div id="environment-header">
|
|
<h2>Environment</h2>
|
|
</div>
|
|
<table id="environment"></table>
|
|
<!-- TEMPLATES -->
|
|
<template id="template_environment_row">
|
|
<tr>
|
|
<td></td>
|
|
<td></td>
|
|
</tr>
|
|
</template>
|
|
<template id="template_results-table__body--empty">
|
|
<tbody class="results-table-row">
|
|
<tr id="not-found-message">
|
|
<td colspan="4">No results found. Check the filters.</td>
|
|
</tr>
|
|
</tbody>
|
|
</template>
|
|
<template id="template_results-table__tbody">
|
|
<tbody class="results-table-row">
|
|
<tr class="collapsible">
|
|
</tr>
|
|
<tr class="extras-row">
|
|
<td class="extra" colspan="4">
|
|
<div class="extraHTML"></div>
|
|
<div class="media">
|
|
<div class="media-container">
|
|
<div class="media-container__nav--left"><</div>
|
|
<div class="media-container__viewport">
|
|
<img src="" />
|
|
<video controls>
|
|
<source src="" type="video/mp4">
|
|
</video>
|
|
</div>
|
|
<div class="media-container__nav--right">></div>
|
|
</div>
|
|
<div class="media__name"></div>
|
|
<div class="media__counter"></div>
|
|
</div>
|
|
<div class="logwrapper">
|
|
<div class="logexpander"></div>
|
|
<div class="log"></div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</template>
|
|
<!-- END TEMPLATES -->
|
|
<div class="summary">
|
|
<div class="summary__data">
|
|
<h2>Summary</h2>
|
|
<div class="additional-summary prefix">
|
|
</div>
|
|
<p class="run-count">5 tests took 00:06:24.</p>
|
|
<p class="filter">(Un)check the boxes to filter the results.</p>
|
|
<div class="summary__reload">
|
|
<div class="summary__reload__button hidden" onclick="location.reload()">
|
|
<div>There are still tests running. <br />Reload this page to get the latest results!</div>
|
|
</div>
|
|
</div>
|
|
<div class="summary__spacer"></div>
|
|
<div class="controls">
|
|
<div class="filters">
|
|
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="failed" >
|
|
<span class="failed">4 Failed,</span>
|
|
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="passed" >
|
|
<span class="passed">1 Passed,</span>
|
|
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="skipped" disabled>
|
|
<span class="skipped">0 Skipped,</span>
|
|
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="xfailed" disabled>
|
|
<span class="xfailed">0 Expected failures,</span>
|
|
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="xpassed" disabled>
|
|
<span class="xpassed">0 Unexpected passes,</span>
|
|
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="error" disabled>
|
|
<span class="error">0 Errors,</span>
|
|
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="rerun" disabled>
|
|
<span class="rerun">0 Reruns</span>
|
|
<input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="retried" disabled>
|
|
<span class="retried">0 Retried,</span>
|
|
</div>
|
|
<div class="collapse">
|
|
<button id="show_all_details">Show all details</button> / <button id="hide_all_details">Hide all details</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="additional-summary summary">
|
|
</div>
|
|
<div class="additional-summary postfix">
|
|
</div>
|
|
</div>
|
|
<table id="results-table">
|
|
<thead id="results-table-head">
|
|
<tr>
|
|
<th class="sortable" data-column-type="result">Result</th>
|
|
<th class="sortable" data-column-type="testId">Test</th>
|
|
<th class="sortable" data-column-type="duration">Duration</th>
|
|
<th>Links</th>
|
|
</tr>
|
|
</thead>
|
|
</table>
|
|
<footer>
|
|
<div id="data-container" data-jsonblob="{"environment": {"Python": "3.12.10", "Platform": "Windows-11-10.0.26200-SP0", "Packages": {"pytest": "9.1.1", "pluggy": "1.6.0"}, "Plugins": {"allure-pytest": "2.16.0", "anyio": "4.14.2", "html": "4.2.0", "metadata": "3.1.1", "timeout": "2.4.0"}, "JAVA_HOME": "C:\\Program Files\\Java\\latest\\jdk-17"}, "tests": {"cross_end/test_driver_to_settlement.py::TestDriverToSettlement::test_driver_to_settlement_full_flow": [{"extras": [], "result": "Failed", "testId": "cross_end/test_driver_to_settlement.py::TestDriverToSettlement::test_driver_to_settlement_full_flow", "duration": "00:01:13", "resultsTableRow": ["<td class=\"col-result\">Failed</td>", "<td class=\"col-testId\">cross_end/test_driver_to_settlement.py::TestDriverToSettlement::test_driver_to_settlement_full_flow</td>", "<td class=\"col-duration\">00:01:13</td>", "<td class=\"col-links\"></td>"], "log": "tests\\cross_end\\test_driver_to_settlement.py:126: in &lt;lambda&gt;\n retry_sql=lambda: update_risk_control_status(transport_order_no, status=1),\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\ntests\\core\\db_helper.py:75: in update_risk_control_status\n return execute_sql(sql, (status, order_code), config)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\ntests\\core\\db_helper.py:32: in execute_sql\n conn = get_connection(config)\n ^^^^^^^^^^^^^^^^^^^^^^\ntests\\core\\db_helper.py:27: in get_connection\n return pymysql.connect(**_resolve_config(config))\n ^^^^^^^^^^^^^^^^^^^^^^^\ntests\\core\\db_helper.py:20: in _resolve_config\n cfg = dict(load_config()[&quot;database&quot;])\n ^^^^^^^^^^^^^^^^^^^^^^^^^\nE KeyError: &#x27;database&#x27;\n\nDuring handling of the above exception, another exception occurred:\ntests\\cross_end\\test_driver_to_settlement.py:124: in test_driver_to_settlement_full_flow\n admin_accounting.do_freight_accounting(\ntests\\pages\\admin\\freight_accounting_page.py:149: in do_freight_accounting\n raise RuntimeError(f&quot;\u8fd0\u8d39\u6838\u7b97 SQL \u91cd\u8bd5\u5931\u8d25\uff01\u5355\u53f7: {order_no}, \u9519\u8bef: {e}&quot;)\nE RuntimeError: \u8fd0\u8d39\u6838\u7b97 SQL \u91cd\u8bd5\u5931\u8d25\uff01\u5355\u53f7: RHJC040026090100010951040132, \u9519\u8bef: &#x27;database&#x27;\n\n------------------------------ Captured log call -------------------------------\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:83 ============================================================\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:84 \u8de8\u7aef E2E: \u53f8\u673aAPP \u2192 \u7ba1\u7406\u7aef\u6838\u7b97\u6253\u6b3e \u5b8c\u6574\u6d41\u7a0b\u5f00\u59cb\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:85 ============================================================\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:102 -&gt; \u63a5\u5355\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:107 -&gt; \u88c5\u8d27\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:112 -&gt; \u5378\u8d27\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:117 -&gt; \u8fd0\u8f93\u5355\u53f7: RHJC040026090100010951040132\n\n"}], "cross_end/test_driver_to_settlement.py::TestDriverToSettlementTeamLeader::test_driver_to_settlement_team_leader_flow": [{"extras": [], "result": "Failed", "testId": "cross_end/test_driver_to_settlement.py::TestDriverToSettlementTeamLeader::test_driver_to_settlement_team_leader_flow", "duration": "00:01:06", "resultsTableRow": ["<td class=\"col-result\">Failed</td>", "<td class=\"col-testId\">cross_end/test_driver_to_settlement.py::TestDriverToSettlementTeamLeader::test_driver_to_settlement_team_leader_flow</td>", "<td class=\"col-duration\">00:01:06</td>", "<td class=\"col-links\"></td>"], "log": "tests\\cross_end\\test_driver_to_settlement.py:239: in &lt;lambda&gt;\n retry_sql=lambda: update_risk_control_status(transport_order_no, status=1),\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\ntests\\core\\db_helper.py:75: in update_risk_control_status\n return execute_sql(sql, (status, order_code), config)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\ntests\\core\\db_helper.py:32: in execute_sql\n conn = get_connection(config)\n ^^^^^^^^^^^^^^^^^^^^^^\ntests\\core\\db_helper.py:27: in get_connection\n return pymysql.connect(**_resolve_config(config))\n ^^^^^^^^^^^^^^^^^^^^^^^\ntests\\core\\db_helper.py:20: in _resolve_config\n cfg = dict(load_config()[&quot;database&quot;])\n ^^^^^^^^^^^^^^^^^^^^^^^^^\nE KeyError: &#x27;database&#x27;\n\nDuring handling of the above exception, another exception occurred:\ntests\\cross_end\\test_driver_to_settlement.py:237: in test_driver_to_settlement_team_leader_flow\n admin_accounting.do_freight_accounting(\ntests\\pages\\admin\\freight_accounting_page.py:149: in do_freight_accounting\n raise RuntimeError(f&quot;\u8fd0\u8d39\u6838\u7b97 SQL \u91cd\u8bd5\u5931\u8d25\uff01\u5355\u53f7: {order_no}, \u9519\u8bef: {e}&quot;)\nE RuntimeError: \u8fd0\u8d39\u6838\u7b97 SQL \u91cd\u8bd5\u5931\u8d25\uff01\u5355\u53f7: RHJC040026090100010952080133, \u9519\u8bef: &#x27;database&#x27;\n\n------------------------------ Captured log call -------------------------------\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:196 ============================================================\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:197 \u8de8\u7aef E2E: \u53f8\u673aAPP \u2192 \u7ba1\u7406\u7aef\u6838\u7b97\u6253\u6b3e\uff08\u8f66\u961f\u957f\u5168\u989d\u6536\u6b3e\uff09\u5b8c\u6574\u6d41\u7a0b\u5f00\u59cb\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:198 ============================================================\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:215 -&gt; \u63a5\u5355\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:220 -&gt; \u88c5\u8d27\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:225 -&gt; \u5378\u8d27\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:230 -&gt; \u8fd0\u8f93\u5355\u53f7: RHJC040026090100010952080133\n\n"}], "cross_end/tests/test_driver_to_settlement.py::TestDriverToSettlement::test_driver_to_settlement_full_flow": [{"extras": [], "result": "Passed", "testId": "cross_end/tests/test_driver_to_settlement.py::TestDriverToSettlement::test_driver_to_settlement_full_flow", "duration": "00:01:40", "resultsTableRow": ["<td class=\"col-result\">Passed</td>", "<td class=\"col-testId\">cross_end/tests/test_driver_to_settlement.py::TestDriverToSettlement::test_driver_to_settlement_full_flow</td>", "<td class=\"col-duration\">00:01:40</td>", "<td class=\"col-links\"></td>"], "log": "------------------------------ Captured log call -------------------------------\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:75 ============================================================\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:76 \u8de8\u7aef E2E: \u53f8\u673aAPP \u2192 \u7ba1\u7406\u7aef\u6838\u7b97\u6253\u6b3e \u5b8c\u6574\u6d41\u7a0b\u5f00\u59cb\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:77 ============================================================\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:91 Phase 1: \u53f8\u673a APP \u2014 \u5f00\u59cb\u63a5\u5355\u6d41\u7a0b\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:94 -&gt; \u63a5\u5355\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:96 Phase 1: \u53f8\u673a APP \u2014 \u5f00\u59cb\u88c5\u8d27\u6d41\u7a0b\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:99 -&gt; \u88c5\u8d27\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:101 Phase 1: \u53f8\u673a APP \u2014 \u5f00\u59cb\u5378\u8d27\u6d41\u7a0b\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:104 -&gt; \u5378\u8d27\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:106 Phase 1: \u53f8\u673a APP \u2014 \u83b7\u53d6\u8fd0\u8f93\u5355\u53f7\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:109 -&gt; \u8fd0\u8f93\u5355\u53f7: RHJC040026090100010953150134\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:115 Phase 2: Web\u7ba1\u7406\u7aef \u2014 \u8fd0\u8d39\u6838\u7b97\uff08\u9996\u6b21\u67e5\u8be2\u65e0\u6570\u636e\u65f6\u81ea\u52a8 SQL \u91cd\u8bd5\uff09\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:121 -&gt; \u8fd0\u8d39\u6838\u7b97\u5b8c\u6210: RHJC040026090100010953150134\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:123 Phase 2: Web\u7ba1\u7406\u7aef \u2014 \u8fd0\u5355\u5ba1\u6838\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:126 -&gt; \u8fd0\u5355\u5ba1\u6838\u5b8c\u6210: RHJC040026090100010953150134\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:128 Phase 2: Web\u7ba1\u7406\u7aef \u2014 \u57ab\u8d44-\u8d22\u52a1\u6253\u6b3e\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:130 -&gt; \u57ab\u8d44-\u8d22\u52a1\u6253\u6b3e\u5b8c\u6210: RHJC040026090100010953150134\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:132 Phase 2: Web\u7ba1\u7406\u7aef \u2014 \u57ab\u8d44-\u8d27\u4e3b\u6253\u6b3e\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:134 -&gt; \u57ab\u8d44-\u8d27\u4e3b\u6253\u6b3e\u5b8c\u6210: RHJC040026090100010953150134\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:139 ============================================================\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:140 \u8de8\u7aef E2E: \u53f8\u673aAPP \u2192 \u7ba1\u7406\u7aef\u6838\u7b97\u6253\u6b3e \u5b8c\u6574\u6d41\u7a0b\u7ed3\u675f!\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:141 \u8fd0\u8f93\u5355\u53f7: RHJC040026090100010953150134\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:142 \u6838\u7b97\u5b8c\u6210: True\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:143 \u5ba1\u6838\u5b8c\u6210: True\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:144 ============================================================\n\n"}], "cross_end/tests/test_driver_to_settlement.py::TestDriverToSettlementTeamLeader::test_driver_to_settlement_team_leader_flow": [{"extras": [], "result": "Failed", "testId": "cross_end/tests/test_driver_to_settlement.py::TestDriverToSettlementTeamLeader::test_driver_to_settlement_team_leader_flow", "duration": "00:01:43", "resultsTableRow": ["<td class=\"col-result\">Failed</td>", "<td class=\"col-testId\">cross_end/tests/test_driver_to_settlement.py::TestDriverToSettlementTeamLeader::test_driver_to_settlement_team_leader_flow</td>", "<td class=\"col-duration\">00:01:43</td>", "<td class=\"col-links\"></td>"], "log": "tests\\cross_end\\tests\\test_driver_to_settlement.py:239: in test_driver_to_settlement_team_leader_flow\n admin_accounting.do_finance_payment(transport_order_no, verify_code=&quot;1234&quot;)\ntests\\cross_end\\pages\\admin\\freight_accounting_page.py:266: in do_finance_payment\n self._click_table_action(button_index=2)\ntests\\cross_end\\pages\\admin\\freight_accounting_page.py:82: in _click_table_action\n self.page.wait_for_selector(&quot;table tbody tr&quot;, timeout=5_000)\nC:\\Users\\23184\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\playwright\\sync_api\\_generated.py:9033: in wait_for_selector\n self._sync(\nC:\\Users\\23184\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\playwright\\_impl\\_page.py:427: in wait_for_selector\n return await self._main_frame.wait_for_selector(**locals_to_params(locals()))\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nC:\\Users\\23184\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\playwright\\_impl\\_frame.py:394: in wait_for_selector\n await self._channel.send(\nC:\\Users\\23184\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\playwright\\_impl\\_connection.py:76: in send\n return await self._connection.wrap_api_call(\nC:\\Users\\23184\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\playwright\\_impl\\_connection.py:632: in wrap_api_call\n raise rewrite_error(error, f&quot;{parsed_st[&#x27;apiName&#x27;]}: {error}&quot;) from None\nE playwright._impl._errors.TimeoutError: Page.wait_for_selector: Timeout 5000ms exceeded.\nE Call log:\nE - waiting for locator(&quot;table tbody tr&quot;) to be visible\nE 14 \u00d7 locator resolved to 5 elements. Proceeding with the first one: &lt;tr&gt;\u2026&lt;/tr&gt;\n\n------------------------------ Captured log call -------------------------------\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:184 ============================================================\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:185 \u8de8\u7aef E2E: \u53f8\u673aAPP \u2192 \u7ba1\u7406\u7aef\u6838\u7b97\u6253\u6b3e\uff08\u8f66\u961f\u957f\u5168\u989d\u6536\u6b3e\uff09\u5b8c\u6574\u6d41\u7a0b\u5f00\u59cb\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:186 ============================================================\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:200 Phase 1: \u53f8\u673a APP \u2014 \u5f00\u59cb\u63a5\u5355\u6d41\u7a0b\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:203 -&gt; \u63a5\u5355\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:205 Phase 1: \u53f8\u673a APP \u2014 \u5f00\u59cb\u88c5\u8d27\u6d41\u7a0b\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:208 -&gt; \u88c5\u8d27\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:210 Phase 1: \u53f8\u673a APP \u2014 \u5f00\u59cb\u5378\u8d27\u6d41\u7a0b\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:213 -&gt; \u5378\u8d27\u6d41\u7a0b\u5b8c\u6210\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:215 Phase 1: \u53f8\u673a APP \u2014 \u83b7\u53d6\u8fd0\u8f93\u5355\u53f7\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:218 -&gt; \u8fd0\u8f93\u5355\u53f7: RHJC040026090100010954560135\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:224 Phase 2: Web\u7ba1\u7406\u7aef \u2014 \u8fd0\u8d39\u6838\u7b97\uff08\u8f66\u961f\u957f\u5168\u989d\u6536\u6b3e\u6a21\u5f0f\uff09\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:231 -&gt; \u8fd0\u8d39\u6838\u7b97\u5b8c\u6210\uff08\u8f66\u961f\u957f\u5168\u989d\u6536\u6b3e\uff09: RHJC040026090100010954560135\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:233 Phase 2: Web\u7ba1\u7406\u7aef \u2014 \u8fd0\u5355\u5ba1\u6838\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:236 -&gt; \u8fd0\u5355\u5ba1\u6838\u5b8c\u6210: RHJC040026090100010954560135\nINFO tests.cross_end.test_driver_to_settlement:test_driver_to_settlement.py:238 Phase 2: Web\u7ba1\u7406\u7aef \u2014 \u57ab\u8d44-\u8d22\u52a1\u6253\u6b3e\n\n"}], "cross_end/tests/test_freight_full_flow.py::TestFreightFullFlow::test_full_freight_flow": [{"extras": [], "result": "Failed", "testId": "cross_end/tests/test_freight_full_flow.py::TestFreightFullFlow::test_full_freight_flow", "duration": "00:00:42", "resultsTableRow": ["<td class=\"col-result\">Failed</td>", "<td class=\"col-testId\">cross_end/tests/test_freight_full_flow.py::TestFreightFullFlow::test_full_freight_flow</td>", "<td class=\"col-duration\">00:00:42</td>", "<td class=\"col-links\"></td>"], "log": "tests\\cross_end\\tests\\test_freight_full_flow.py:67: in test_full_freight_flow\n navbar.go_to_submenu(&quot;\u8fd0\u8f93\u7ba1\u7406&quot;, &quot;\u8d27\u6e90\u7ba1\u7406&quot;)\ntests\\web\\admin\\pages\\components\\navbar.py:42: in go_to_submenu\n self.go_to(parent_menu)\ntests\\web\\admin\\pages\\components\\navbar.py:33: in go_to\n self.click_when_ready(menu_item)\ntests\\web\\admin\\pages\\base_page.py:53: in click_when_ready\n self.wait_visible(locator)\ntests\\web\\admin\\pages\\base_page.py:33: in wait_visible\n expect(locator).to_be_visible(timeout=timeout_ms or self.timeout)\nE AssertionError: Locator expected to be visible\nE Actual value: hidden \nE Call log:\nE - Expect &quot;to_be_visible&quot; with timeout 30000ms\nE - waiting for get_by_text(&quot;\u8fd0\u8f93\u7ba1\u7406&quot;).first\nE 60 \u00d7 locator resolved to &lt;span&gt;\u8fd0\u8f93\u7ba1\u7406&lt;/span&gt;\nE - unexpected value &quot;hidden&quot;\nE \nE Aria snapshot:\nE - text: \ue6ca\nE - heading &quot;\u7cfb\u7edf\u5e03\u5c40\u914d\u7f6e&quot; [level=3]\nE - text: \u4e3b\u9898\u8272 \ue6df \u5f00\u542f Tags-View\nE - switch [checked]:\nE - checkbox [checked]\nE - text: \u56fa\u5b9a Header\nE - switch:\nE - checkbox\nE - text: \u4fa7\u8fb9\u680f Logo\nE - switch [checked]:\nE - checkbox [checked]\nE - img\nE - link:\nE - /url: &quot;#/&quot;\nE - img\nE - menubar:\nE - link &quot;\u9996\u9875&quot;:\nE - /url: &quot;#/home&quot;\nE - menuitem &quot;\u9996\u9875&quot;\nE - menuitem &quot;\u57fa\u7840\u4fe1\u606f \ue6df&quot;\nE - menuitem &quot;\u6574\u8f66\u8fd0\u8f93 \ue6df&quot;\nE - menuitem &quot;\u7ba1\u7406\u670d\u52a1 \ue6df&quot;\nE - menuitem &quot;\u540c\u57ce\u8fd0\u8f93 \ue6df&quot;\nE - menuitem &quot;\u589e\u503c\u670d\u52a1 \ue6df&quot;\nE - menuitem &quot;\u8d22\u52a1\u7edf\u8ba1 \ue6df&quot;\nE - menuitem &quot;\u7cfb\u7edf\u7ba1\u7406 \ue6df&quot;\nE - link &quot;\u8d44\u6e90\u63a7\u5236&quot;:\nE - /url: &quot;#/resourceController&quot;\nE - menuitem &quot;\u8d44\u6e90\u63a7\u5236&quot;\nE - img\nE - navigation &quot;Breadcrumb&quot;:\nE - link &quot;\u9996\u9875&quot;\nE - textbox &quot;\u641c\u7d22&quot;\nE - button &quot;\u8d85\u7ea7\u7ba1\u7406\u5458 \ue790&quot;:\nE - text: \u8d85\u7ea7\u7ba1\u7406\u5458\nE - emphasis: \ue790\nE - text: \u9996\u9875 RELEASE SOURSE \u53d1\u5e03\u8d27\u6e90 ORDER MANAGEMENT \u8ba2\u5355\u7ba1\u7406 CUSTOMER SERVICE \u5ba2\u670d\u7ba1\u7406\nE - link &quot;BIG DATA CENTER \u5927\u6570\u636e\u4e2d\u5fc3&quot;:\nE - /url: https://ybxcx.ynyun8.com:8000/Bigdata/\nE - link &quot;ZHONGMIN BIG DATA \u4e2d\u95fd\u5927\u6570\u636e&quot;:\nE - /url: https://ybxcx.ynyun8.com:8000/zm/\nE - img\nE - text: ZHONGMIN BIG DATA \u4e2d\u95fd\u5927\u6570\u636e\nE - text: \u8fd0\u8f93\u6d41\u7a0b \u5b98\u7f51 \u8d27\u6e90\u53d1\u5e03 \u8d27\u6e90\u5ba1\u6838 \u8d27\u6e90\u7ba1\u7406 \u8fd0\u8f93\u6838\u7b97 \u8fd0\u5355\u7ba1\u7406 \u9884\u4ed8 \u8d22\u52a1\u6253\u6b3e\nE - link:\nE - /url: https://www.ynyun8.com/\nE - text: \u5e73\u53f0\u8f66\u8f86 \u5e73\u53f0\u8ba2\u5355 \u5e73\u53f0\u8f66\u8f86\u603b\u8ba1 12,832 \u5e73\u53f0\u8f66\u8f86 153,938 \u5e73\u53f0\u8fd0\u529b(\u5428) \u8d27\u6e90\u5355\u603b\u6570\uff1a 9948 1763 \u672a\u63a5\u5355 116 \u5df2\u63a5\u5355 1347 \u5df2\u7ed3\u7b97 30 \u5df2\u64a4\u9500 \u8fd0\u8f93\u5355\u603b\u6570\uff1a 29939 10073 \u5f85\u8fd0\u8f93 460 \u8fd0\u8f93\u4e2d 7189 \u5df2\u5378\u8d27 \u8d27\u6e90\u5355\u7c7b\u578b 9519 \u6b63\u5e38\u5355 138 \u5185\u5bb9\u62a5\u4ef7\u5355 \u4ea4\u6613\u6570\u636e \u5145\u503c\u603b\u989d(\u5143) 158,232 \u4ea4\u6613\u603b\u989d(\u5143) 117,687,373 \u8fd0\u8d39\u603b\u989d(\u5143) 37,980,387 \u5f00\u7968\u603b\u989d(\u5143) 838,947\n\n------------------------------ Captured log call -------------------------------\nINFO tests.cross_end.test_freight_full_flow:test_freight_full_flow.py:48 ============================================================\nINFO tests.cross_end.test_freight_full_flow:test_freight_full_flow.py:49 \u8de8\u7aef E2E: \u8d27\u8fd0\u5168\u6d41\u7a0b\u5f00\u59cb\nINFO tests.cross_end.test_freight_full_flow:test_freight_full_flow.py:50 ============================================================\nINFO tests.cross_end.test_freight_full_flow:test_freight_full_flow.py:64 Step 1/8: Web\u7ba1\u7406\u7aef \u2014 \u53d1\u5e03\u8d27\u6e90\n\n"}]}, "renderCollapsed": ["passed"], "initialSort": "result", "title": "report_20260902_095358.html"}"></div>
|
|
<script>
|
|
(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){
|
|
const { getCollapsedCategory, setCollapsedIds } = require('./storage.js')
|
|
|
|
class DataManager {
|
|
setManager(data) {
|
|
const collapsedCategories = [...getCollapsedCategory(data.renderCollapsed)]
|
|
const collapsedIds = []
|
|
const tests = Object.values(data.tests).flat().map((test, index) => {
|
|
const collapsed = collapsedCategories.includes(test.result.toLowerCase())
|
|
const id = `test_${index}`
|
|
if (collapsed) {
|
|
collapsedIds.push(id)
|
|
}
|
|
return {
|
|
...test,
|
|
id,
|
|
collapsed,
|
|
}
|
|
})
|
|
const dataBlob = { ...data, tests }
|
|
this.data = { ...dataBlob }
|
|
this.renderData = { ...dataBlob }
|
|
setCollapsedIds(collapsedIds)
|
|
}
|
|
|
|
get allData() {
|
|
return { ...this.data }
|
|
}
|
|
|
|
resetRender() {
|
|
this.renderData = { ...this.data }
|
|
}
|
|
|
|
setRender(data) {
|
|
this.renderData.tests = [...data]
|
|
}
|
|
|
|
toggleCollapsedItem(id) {
|
|
this.renderData.tests = this.renderData.tests.map((test) =>
|
|
test.id === id ? { ...test, collapsed: !test.collapsed } : test,
|
|
)
|
|
}
|
|
|
|
set allCollapsed(collapsed) {
|
|
this.renderData = { ...this.renderData, tests: [...this.renderData.tests.map((test) => (
|
|
{ ...test, collapsed }
|
|
))] }
|
|
}
|
|
|
|
get testSubset() {
|
|
return [...this.renderData.tests]
|
|
}
|
|
|
|
get environment() {
|
|
return this.renderData.environment
|
|
}
|
|
|
|
get initialSort() {
|
|
return this.data.initialSort
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
manager: new DataManager(),
|
|
}
|
|
|
|
},{"./storage.js":8}],2:[function(require,module,exports){
|
|
const mediaViewer = require('./mediaviewer.js')
|
|
const templateEnvRow = document.getElementById('template_environment_row')
|
|
const templateResult = document.getElementById('template_results-table__tbody')
|
|
|
|
function htmlToElements(html) {
|
|
const temp = document.createElement('template')
|
|
temp.innerHTML = html
|
|
return temp.content.childNodes
|
|
}
|
|
|
|
const find = (selector, elem) => {
|
|
if (!elem) {
|
|
elem = document
|
|
}
|
|
return elem.querySelector(selector)
|
|
}
|
|
|
|
const findAll = (selector, elem) => {
|
|
if (!elem) {
|
|
elem = document
|
|
}
|
|
return [...elem.querySelectorAll(selector)]
|
|
}
|
|
|
|
const dom = {
|
|
getStaticRow: (key, value) => {
|
|
const envRow = templateEnvRow.content.cloneNode(true)
|
|
const isObj = typeof value === 'object' && value !== null
|
|
const values = isObj ? Object.keys(value).map((k) => `${k}: ${value[k]}`) : null
|
|
|
|
const valuesElement = htmlToElements(
|
|
values ? `<ul>${values.map((val) => `<li>${val}</li>`).join('')}<ul>` : `<div>${value}</div>`)[0]
|
|
const td = findAll('td', envRow)
|
|
td[0].textContent = key
|
|
td[1].appendChild(valuesElement)
|
|
|
|
return envRow
|
|
},
|
|
getResultTBody: ({ testId, id, log, extras, resultsTableRow, tableHtml, result, collapsed }) => {
|
|
const resultBody = templateResult.content.cloneNode(true)
|
|
resultBody.querySelector('tbody').classList.add(result.toLowerCase())
|
|
resultBody.querySelector('tbody').id = testId
|
|
resultBody.querySelector('.collapsible').dataset.id = id
|
|
|
|
resultsTableRow.forEach((html) => {
|
|
const t = document.createElement('template')
|
|
t.innerHTML = html
|
|
resultBody.querySelector('.collapsible').appendChild(t.content)
|
|
})
|
|
|
|
if (log) {
|
|
// Wrap lines starting with "E" with span.error to color those lines red
|
|
const wrappedLog = log.replace(/^E.*$/gm, (match) => `<span class="error">${match}</span>`)
|
|
resultBody.querySelector('.log').innerHTML = wrappedLog
|
|
} else {
|
|
resultBody.querySelector('.log').remove()
|
|
}
|
|
|
|
if (collapsed) {
|
|
resultBody.querySelector('.collapsible > .col-result')?.classList.add('collapsed')
|
|
resultBody.querySelector('.extras-row').classList.add('hidden')
|
|
} else {
|
|
resultBody.querySelector('.collapsible > .col-result')?.classList.remove('collapsed')
|
|
}
|
|
|
|
const media = []
|
|
extras?.forEach(({ name, format_type, content }) => {
|
|
if (['image', 'video'].includes(format_type)) {
|
|
media.push({ path: content, name, format_type })
|
|
}
|
|
|
|
if (format_type === 'html') {
|
|
resultBody.querySelector('.extraHTML').insertAdjacentHTML('beforeend', `<div>${content}</div>`)
|
|
}
|
|
})
|
|
mediaViewer.setup(resultBody, media)
|
|
|
|
// Add custom html from the pytest_html_results_table_html hook
|
|
tableHtml?.forEach((item) => {
|
|
resultBody.querySelector('td[class="extra"]').insertAdjacentHTML('beforeend', item)
|
|
})
|
|
|
|
return resultBody
|
|
},
|
|
}
|
|
|
|
module.exports = {
|
|
dom,
|
|
htmlToElements,
|
|
find,
|
|
findAll,
|
|
}
|
|
|
|
},{"./mediaviewer.js":6}],3:[function(require,module,exports){
|
|
const { manager } = require('./datamanager.js')
|
|
const { doSort } = require('./sort.js')
|
|
const storageModule = require('./storage.js')
|
|
|
|
const getFilteredSubSet = (filter) =>
|
|
manager.allData.tests.filter(({ result }) => filter.includes(result.toLowerCase()))
|
|
|
|
const doInitFilter = () => {
|
|
const currentFilter = storageModule.getVisible()
|
|
const filteredSubset = getFilteredSubSet(currentFilter)
|
|
manager.setRender(filteredSubset)
|
|
}
|
|
|
|
const doFilter = (type, show) => {
|
|
if (show) {
|
|
storageModule.showCategory(type)
|
|
} else {
|
|
storageModule.hideCategory(type)
|
|
}
|
|
|
|
const currentFilter = storageModule.getVisible()
|
|
const filteredSubset = getFilteredSubSet(currentFilter)
|
|
manager.setRender(filteredSubset)
|
|
|
|
const sortColumn = storageModule.getSort()
|
|
doSort(sortColumn, true)
|
|
}
|
|
|
|
module.exports = {
|
|
doFilter,
|
|
doInitFilter,
|
|
}
|
|
|
|
},{"./datamanager.js":1,"./sort.js":7,"./storage.js":8}],4:[function(require,module,exports){
|
|
const { redraw, bindEvents, renderStatic } = require('./main.js')
|
|
const { doInitFilter } = require('./filter.js')
|
|
const { doInitSort } = require('./sort.js')
|
|
const { manager } = require('./datamanager.js')
|
|
const data = JSON.parse(document.getElementById('data-container').dataset.jsonblob)
|
|
|
|
function init() {
|
|
manager.setManager(data)
|
|
doInitFilter()
|
|
doInitSort()
|
|
renderStatic()
|
|
redraw()
|
|
bindEvents()
|
|
}
|
|
|
|
init()
|
|
|
|
},{"./datamanager.js":1,"./filter.js":3,"./main.js":5,"./sort.js":7}],5:[function(require,module,exports){
|
|
const { dom, find, findAll } = require('./dom.js')
|
|
const { manager } = require('./datamanager.js')
|
|
const { doSort } = require('./sort.js')
|
|
const { doFilter } = require('./filter.js')
|
|
const {
|
|
getVisible,
|
|
getCollapsedIds,
|
|
setCollapsedIds,
|
|
getSort,
|
|
getSortDirection,
|
|
possibleFilters,
|
|
} = require('./storage.js')
|
|
|
|
const removeChildren = (node) => {
|
|
while (node.firstChild) {
|
|
node.removeChild(node.firstChild)
|
|
}
|
|
}
|
|
|
|
const renderStatic = () => {
|
|
const renderEnvironmentTable = () => {
|
|
const environment = manager.environment
|
|
const rows = Object.keys(environment).map((key) => dom.getStaticRow(key, environment[key]))
|
|
const table = document.getElementById('environment')
|
|
removeChildren(table)
|
|
rows.forEach((row) => table.appendChild(row))
|
|
}
|
|
renderEnvironmentTable()
|
|
}
|
|
|
|
const addItemToggleListener = (elem) => {
|
|
elem.addEventListener('click', ({ target }) => {
|
|
const id = target.parentElement.dataset.id
|
|
manager.toggleCollapsedItem(id)
|
|
|
|
const collapsedIds = getCollapsedIds()
|
|
if (collapsedIds.includes(id)) {
|
|
const updated = collapsedIds.filter((item) => item !== id)
|
|
setCollapsedIds(updated)
|
|
} else {
|
|
collapsedIds.push(id)
|
|
setCollapsedIds(collapsedIds)
|
|
}
|
|
redraw()
|
|
})
|
|
}
|
|
|
|
const renderContent = (tests) => {
|
|
const sortAttr = getSort(manager.initialSort)
|
|
const sortAsc = JSON.parse(getSortDirection())
|
|
const rows = tests.map(dom.getResultTBody)
|
|
const table = document.getElementById('results-table')
|
|
const tableHeader = document.getElementById('results-table-head')
|
|
|
|
const newTable = document.createElement('table')
|
|
newTable.id = 'results-table'
|
|
|
|
// remove all sorting classes and set the relevant
|
|
findAll('.sortable', tableHeader).forEach((elem) => elem.classList.remove('asc', 'desc'))
|
|
tableHeader.querySelector(`.sortable[data-column-type="${sortAttr}"]`)?.classList.add(sortAsc ? 'desc' : 'asc')
|
|
newTable.appendChild(tableHeader)
|
|
|
|
if (!rows.length) {
|
|
const emptyTable = document.getElementById('template_results-table__body--empty').content.cloneNode(true)
|
|
newTable.appendChild(emptyTable)
|
|
} else {
|
|
rows.forEach((row) => {
|
|
if (!!row) {
|
|
findAll('.collapsible td:not(.col-links', row).forEach(addItemToggleListener)
|
|
find('.logexpander', row).addEventListener('click',
|
|
(evt) => evt.target.parentNode.classList.toggle('expanded'),
|
|
)
|
|
newTable.appendChild(row)
|
|
}
|
|
})
|
|
}
|
|
|
|
table.replaceWith(newTable)
|
|
}
|
|
|
|
const renderDerived = () => {
|
|
const currentFilter = getVisible()
|
|
possibleFilters.forEach((result) => {
|
|
const input = document.querySelector(`input[data-test-result="${result}"]`)
|
|
input.checked = currentFilter.includes(result)
|
|
})
|
|
}
|
|
|
|
const bindEvents = () => {
|
|
const filterColumn = (evt) => {
|
|
const { target: element } = evt
|
|
const { testResult } = element.dataset
|
|
|
|
doFilter(testResult, element.checked)
|
|
const collapsedIds = getCollapsedIds()
|
|
const updated = manager.renderData.tests.map((test) => {
|
|
return {
|
|
...test,
|
|
collapsed: collapsedIds.includes(test.id),
|
|
}
|
|
})
|
|
manager.setRender(updated)
|
|
redraw()
|
|
}
|
|
|
|
const header = document.getElementById('environment-header')
|
|
header.addEventListener('click', () => {
|
|
const table = document.getElementById('environment')
|
|
table.classList.toggle('hidden')
|
|
header.classList.toggle('collapsed')
|
|
})
|
|
|
|
findAll('input[name="filter_checkbox"]').forEach((elem) => {
|
|
elem.addEventListener('click', filterColumn)
|
|
})
|
|
|
|
findAll('.sortable').forEach((elem) => {
|
|
elem.addEventListener('click', (evt) => {
|
|
const { target: element } = evt
|
|
const { columnType } = element.dataset
|
|
doSort(columnType)
|
|
redraw()
|
|
})
|
|
})
|
|
|
|
document.getElementById('show_all_details').addEventListener('click', () => {
|
|
manager.allCollapsed = false
|
|
setCollapsedIds([])
|
|
redraw()
|
|
})
|
|
document.getElementById('hide_all_details').addEventListener('click', () => {
|
|
manager.allCollapsed = true
|
|
const allIds = manager.renderData.tests.map((test) => test.id)
|
|
setCollapsedIds(allIds)
|
|
redraw()
|
|
})
|
|
}
|
|
|
|
const redraw = () => {
|
|
const { testSubset } = manager
|
|
|
|
renderContent(testSubset)
|
|
renderDerived()
|
|
}
|
|
|
|
module.exports = {
|
|
redraw,
|
|
bindEvents,
|
|
renderStatic,
|
|
}
|
|
|
|
},{"./datamanager.js":1,"./dom.js":2,"./filter.js":3,"./sort.js":7,"./storage.js":8}],6:[function(require,module,exports){
|
|
class MediaViewer {
|
|
constructor(assets) {
|
|
this.assets = assets
|
|
this.index = 0
|
|
}
|
|
|
|
nextActive() {
|
|
this.index = this.index === this.assets.length - 1 ? 0 : this.index + 1
|
|
return [this.activeFile, this.index]
|
|
}
|
|
|
|
prevActive() {
|
|
this.index = this.index === 0 ? this.assets.length - 1 : this.index -1
|
|
return [this.activeFile, this.index]
|
|
}
|
|
|
|
get currentIndex() {
|
|
return this.index
|
|
}
|
|
|
|
get activeFile() {
|
|
return this.assets[this.index]
|
|
}
|
|
}
|
|
|
|
|
|
const setup = (resultBody, assets) => {
|
|
if (!assets.length) {
|
|
resultBody.querySelector('.media').classList.add('hidden')
|
|
return
|
|
}
|
|
|
|
const mediaViewer = new MediaViewer(assets)
|
|
const container = resultBody.querySelector('.media-container')
|
|
const leftArrow = resultBody.querySelector('.media-container__nav--left')
|
|
const rightArrow = resultBody.querySelector('.media-container__nav--right')
|
|
const mediaName = resultBody.querySelector('.media__name')
|
|
const counter = resultBody.querySelector('.media__counter')
|
|
const imageEl = resultBody.querySelector('img')
|
|
const sourceEl = resultBody.querySelector('source')
|
|
const videoEl = resultBody.querySelector('video')
|
|
|
|
const setImg = (media, index) => {
|
|
if (media?.format_type === 'image') {
|
|
imageEl.src = media.path
|
|
|
|
imageEl.classList.remove('hidden')
|
|
videoEl.classList.add('hidden')
|
|
} else if (media?.format_type === 'video') {
|
|
sourceEl.src = media.path
|
|
|
|
videoEl.classList.remove('hidden')
|
|
imageEl.classList.add('hidden')
|
|
}
|
|
|
|
mediaName.innerText = media?.name
|
|
counter.innerText = `${index + 1} / ${assets.length}`
|
|
}
|
|
setImg(mediaViewer.activeFile, mediaViewer.currentIndex)
|
|
|
|
const moveLeft = () => {
|
|
const [media, index] = mediaViewer.prevActive()
|
|
setImg(media, index)
|
|
}
|
|
const doRight = () => {
|
|
const [media, index] = mediaViewer.nextActive()
|
|
setImg(media, index)
|
|
}
|
|
const openImg = () => {
|
|
window.open(mediaViewer.activeFile.path, '_blank')
|
|
}
|
|
if (assets.length === 1) {
|
|
container.classList.add('media-container--fullscreen')
|
|
} else {
|
|
leftArrow.addEventListener('click', moveLeft)
|
|
rightArrow.addEventListener('click', doRight)
|
|
}
|
|
imageEl.addEventListener('click', openImg)
|
|
}
|
|
|
|
module.exports = {
|
|
setup,
|
|
}
|
|
|
|
},{}],7:[function(require,module,exports){
|
|
const { manager } = require('./datamanager.js')
|
|
const storageModule = require('./storage.js')
|
|
|
|
const genericSort = (list, key, ascending, customOrder) => {
|
|
let sorted
|
|
if (customOrder) {
|
|
sorted = list.sort((a, b) => {
|
|
const aValue = a.result.toLowerCase()
|
|
const bValue = b.result.toLowerCase()
|
|
|
|
const aIndex = customOrder.findIndex((item) => item.toLowerCase() === aValue)
|
|
const bIndex = customOrder.findIndex((item) => item.toLowerCase() === bValue)
|
|
|
|
// Compare the indices to determine the sort order
|
|
return aIndex - bIndex
|
|
})
|
|
} else {
|
|
sorted = list.sort((a, b) => a[key] === b[key] ? 0 : a[key] > b[key] ? 1 : -1)
|
|
}
|
|
|
|
if (ascending) {
|
|
sorted.reverse()
|
|
}
|
|
return sorted
|
|
}
|
|
|
|
const durationSort = (list, ascending) => {
|
|
const parseDuration = (duration) => {
|
|
if (duration.includes(':')) {
|
|
// If it's in the format "HH:mm:ss"
|
|
const [hours, minutes, seconds] = duration.split(':').map(Number)
|
|
return (hours * 3600 + minutes * 60 + seconds) * 1000
|
|
} else {
|
|
// If it's in the format "nnn ms"
|
|
return parseInt(duration)
|
|
}
|
|
}
|
|
const sorted = list.sort((a, b) => parseDuration(a['duration']) - parseDuration(b['duration']))
|
|
if (ascending) {
|
|
sorted.reverse()
|
|
}
|
|
return sorted
|
|
}
|
|
|
|
const doInitSort = () => {
|
|
const type = storageModule.getSort(manager.initialSort)
|
|
const ascending = storageModule.getSortDirection()
|
|
const list = manager.testSubset
|
|
const initialOrder = ['Error', 'Failed', 'Rerun', 'XFailed', 'XPassed', 'Skipped', 'Passed']
|
|
|
|
storageModule.setSort(type)
|
|
storageModule.setSortDirection(ascending)
|
|
|
|
if (type?.toLowerCase() === 'original') {
|
|
manager.setRender(list)
|
|
} else {
|
|
let sortedList
|
|
switch (type) {
|
|
case 'duration':
|
|
sortedList = durationSort(list, ascending)
|
|
break
|
|
case 'result':
|
|
sortedList = genericSort(list, type, ascending, initialOrder)
|
|
break
|
|
default:
|
|
sortedList = genericSort(list, type, ascending)
|
|
break
|
|
}
|
|
manager.setRender(sortedList)
|
|
}
|
|
}
|
|
|
|
const doSort = (type, skipDirection) => {
|
|
const newSortType = storageModule.getSort(manager.initialSort) !== type
|
|
const currentAsc = storageModule.getSortDirection()
|
|
let ascending
|
|
if (skipDirection) {
|
|
ascending = currentAsc
|
|
} else {
|
|
ascending = newSortType ? false : !currentAsc
|
|
}
|
|
storageModule.setSort(type)
|
|
storageModule.setSortDirection(ascending)
|
|
|
|
const list = manager.testSubset
|
|
const sortedList = type === 'duration' ? durationSort(list, ascending) : genericSort(list, type, ascending)
|
|
manager.setRender(sortedList)
|
|
}
|
|
|
|
module.exports = {
|
|
doInitSort,
|
|
doSort,
|
|
}
|
|
|
|
},{"./datamanager.js":1,"./storage.js":8}],8:[function(require,module,exports){
|
|
const possibleFilters = [
|
|
'passed',
|
|
'skipped',
|
|
'failed',
|
|
'error',
|
|
'xfailed',
|
|
'xpassed',
|
|
'rerun',
|
|
]
|
|
|
|
const getVisible = () => {
|
|
const url = new URL(window.location.href)
|
|
const settings = new URLSearchParams(url.search).get('visible')
|
|
const lower = (item) => {
|
|
const lowerItem = item.toLowerCase()
|
|
if (possibleFilters.includes(lowerItem)) {
|
|
return lowerItem
|
|
}
|
|
return null
|
|
}
|
|
return settings === null ?
|
|
possibleFilters :
|
|
[...new Set(settings?.split(',').map(lower).filter((item) => item))]
|
|
}
|
|
|
|
const hideCategory = (categoryToHide) => {
|
|
const url = new URL(window.location.href)
|
|
const visibleParams = new URLSearchParams(url.search).get('visible')
|
|
const currentVisible = visibleParams ? visibleParams.split(',') : [...possibleFilters]
|
|
const settings = [...new Set(currentVisible)].filter((f) => f !== categoryToHide).join(',')
|
|
|
|
url.searchParams.set('visible', settings)
|
|
window.history.pushState({}, null, unescape(url.href))
|
|
}
|
|
|
|
const showCategory = (categoryToShow) => {
|
|
if (typeof window === 'undefined') {
|
|
return
|
|
}
|
|
const url = new URL(window.location.href)
|
|
const currentVisible = new URLSearchParams(url.search).get('visible')?.split(',').filter(Boolean) ||
|
|
[...possibleFilters]
|
|
const settings = [...new Set([categoryToShow, ...currentVisible])]
|
|
const noFilter = possibleFilters.length === settings.length || !settings.length
|
|
|
|
noFilter ? url.searchParams.delete('visible') : url.searchParams.set('visible', settings.join(','))
|
|
window.history.pushState({}, null, unescape(url.href))
|
|
}
|
|
|
|
const getSort = (initialSort) => {
|
|
const url = new URL(window.location.href)
|
|
let sort = new URLSearchParams(url.search).get('sort')
|
|
if (!sort) {
|
|
sort = initialSort || 'result'
|
|
}
|
|
return sort
|
|
}
|
|
|
|
const setSort = (type) => {
|
|
const url = new URL(window.location.href)
|
|
url.searchParams.set('sort', type)
|
|
window.history.pushState({}, null, unescape(url.href))
|
|
}
|
|
|
|
const getCollapsedCategory = (renderCollapsed) => {
|
|
let categories
|
|
if (typeof window !== 'undefined') {
|
|
const url = new URL(window.location.href)
|
|
const collapsedItems = new URLSearchParams(url.search).get('collapsed')
|
|
switch (true) {
|
|
case !renderCollapsed && collapsedItems === null:
|
|
categories = ['passed']
|
|
break
|
|
case collapsedItems?.length === 0 || /^["']{2}$/.test(collapsedItems):
|
|
categories = []
|
|
break
|
|
case /^all$/.test(collapsedItems) || collapsedItems === null && /^all$/.test(renderCollapsed):
|
|
categories = [...possibleFilters]
|
|
break
|
|
default:
|
|
categories = collapsedItems?.split(',').map((item) => item.toLowerCase()) || renderCollapsed
|
|
break
|
|
}
|
|
} else {
|
|
categories = []
|
|
}
|
|
return categories
|
|
}
|
|
|
|
const getSortDirection = () => JSON.parse(sessionStorage.getItem('sortAsc')) || false
|
|
const setSortDirection = (ascending) => sessionStorage.setItem('sortAsc', ascending)
|
|
|
|
const getCollapsedIds = () => JSON.parse(sessionStorage.getItem('collapsedIds')) || []
|
|
const setCollapsedIds = (list) => sessionStorage.setItem('collapsedIds', JSON.stringify(list))
|
|
|
|
module.exports = {
|
|
getVisible,
|
|
hideCategory,
|
|
showCategory,
|
|
getCollapsedIds,
|
|
setCollapsedIds,
|
|
getSort,
|
|
setSort,
|
|
getSortDirection,
|
|
setSortDirection,
|
|
getCollapsedCategory,
|
|
possibleFilters,
|
|
}
|
|
|
|
},{}]},{},[4]);
|
|
</script>
|
|
</footer>
|
|
</body>
|
|
</html> |