qapytest 0.3.3__tar.gz → 0.3.4__tar.gz

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.
Files changed (54) hide show
  1. {qapytest-0.3.3 → qapytest-0.3.4}/CHANGELOG.md +13 -0
  2. {qapytest-0.3.3 → qapytest-0.3.4}/PKG-INFO +1 -1
  3. {qapytest-0.3.3 → qapytest-0.3.4}/pyproject.toml +1 -1
  4. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_assets/_log_tree.html.jinja2 +10 -2
  5. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_assets/report.html.jinja2 +57 -12
  6. qapytest-0.3.4/qapytest/_assets/scripts.js +1201 -0
  7. qapytest-0.3.4/qapytest/_assets/styles.css +1255 -0
  8. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_internal.py +5 -2
  9. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_internal_functions.py +12 -23
  10. {qapytest-0.3.3 → qapytest-0.3.4}/uv.lock +9 -1
  11. qapytest-0.3.3/qapytest/_assets/scripts.js +0 -442
  12. qapytest-0.3.3/qapytest/_assets/styles.css +0 -649
  13. {qapytest-0.3.3 → qapytest-0.3.4}/.gitignore +0 -0
  14. {qapytest-0.3.3 → qapytest-0.3.4}/.pre-commit-config.yaml +0 -0
  15. {qapytest-0.3.3 → qapytest-0.3.4}/LICENSE +0 -0
  16. {qapytest-0.3.3 → qapytest-0.3.4}/NOTICE +0 -0
  17. {qapytest-0.3.3 → qapytest-0.3.4}/README.md +0 -0
  18. {qapytest-0.3.3 → qapytest-0.3.4}/THIRD_PARTY_NOTICES.txt +0 -0
  19. {qapytest-0.3.3 → qapytest-0.3.4}/demo.py +0 -0
  20. {qapytest-0.3.3 → qapytest-0.3.4}/docs/API.md +0 -0
  21. {qapytest-0.3.3 → qapytest-0.3.4}/docs/CLI.md +0 -0
  22. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/faker.LICENSE +0 -0
  23. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/httpx.LICENSE +0 -0
  24. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/jinja2.LICENSE +0 -0
  25. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/jsonschema.LICENSE +0 -0
  26. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/playwright-python.LICENSE +0 -0
  27. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/pytest-playwright.LICENSE +0 -0
  28. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/pytest.LICENSE +0 -0
  29. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/python_dotenv.LICENSE +0 -0
  30. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/redis-py.LICENSE +0 -0
  31. {qapytest-0.3.3 → qapytest-0.3.4}/licenses/sqlalchemy.LICENSE +0 -0
  32. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/__init__.py +0 -0
  33. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_attach.py +0 -0
  34. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_client_http.py +0 -0
  35. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_config.py +0 -0
  36. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_config_http.py +0 -0
  37. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_json_validation.py +0 -0
  38. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_plugin.py +0 -0
  39. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_redis.py +0 -0
  40. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_report.py +0 -0
  41. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_soft_assert.py +0 -0
  42. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_sql.py +0 -0
  43. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/_step.py +0 -0
  44. {qapytest-0.3.3 → qapytest-0.3.4}/qapytest/py.typed +0 -0
  45. {qapytest-0.3.3 → qapytest-0.3.4}/tests/encoding.py +0 -0
  46. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_attach.py +0 -0
  47. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_graphql_client.py +0 -0
  48. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_http_client.py +0 -0
  49. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_integration.py +0 -0
  50. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_markers.py +0 -0
  51. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_redis_client.py +0 -0
  52. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_soft_assert.py +0 -0
  53. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_sql_client.py +0 -0
  54. {qapytest-0.3.3 → qapytest-0.3.4}/tests/test_step.py +0 -0
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.4] - 2025-11-23
9
+
10
+ ### Enhanced
11
+
12
+ - 📊 **Interactive HTML Report** - significantly improved report interactivity with new features:
13
+ - **JSON Viewer** - interactive tree view for JSON attachments with search and expand/collapse capabilities
14
+ - **Log Filtering** - filter logs by logger name and level, with search functionality
15
+ - **Component Filtering** - easily filter test results by component
16
+ - **Step Management** - expand/collapse all steps with a single click
17
+ - **Navigation** - added "Scroll to top" button for easier navigation in long reports
18
+ - 🛠️ **Soft Assertions** - improved display of soft assertion failures in the report
19
+
8
20
  ## [0.3.3] - 2025-10-31
9
21
 
10
22
  ### Fixed
@@ -157,6 +169,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
157
169
  - Configurable attachment size limits
158
170
  - Professional report styling with responsive design
159
171
 
172
+ [0.3.4]: https://github.com/o73k51i/qapytest/releases/tag/v0.3.4
160
173
  [0.3.3]: https://github.com/o73k51i/qapytest/releases/tag/v0.3.3
161
174
  [0.3.2]: https://github.com/o73k51i/qapytest/releases/tag/v0.3.2
162
175
  [0.3.1]: https://github.com/o73k51i/qapytest/releases/tag/v0.3.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qapytest
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: A powerful testing framework based on pytest, specifically designed for QA engineers
5
5
  Project-URL: Homepage, https://github.com/o73k51i/qapytest
6
6
  Project-URL: Repository, https://github.com/o73k51i/qapytest
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "qapytest"
3
- version = "0.3.3"
3
+ version = "0.3.4"
4
4
  description = "A powerful testing framework based on pytest, specifically designed for QA engineers"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10,<3.14"
@@ -4,9 +4,17 @@
4
4
  {% for entry in log_list %}
5
5
  {% if entry.type == 'step' %}
6
6
  <li class="{{ 'step-passed' if entry.passed else 'step-failed' }}">
7
+ {% if entry.children %}
8
+ <span class="step-toggle collapsed">▼</span>
9
+ {% else %}
10
+ <span class="step-toggle-placeholder"></span>
11
+ {% endif %}
7
12
  {{ '✔︎' if entry.passed else '✖︎' }} <strong>Step:</strong> {{ entry.message|e }}
8
- {# --- Recursive Call --- #}
9
- {{ render_log_tree(entry.children, test_index, attachment_counter, assert_counter) }}
13
+ {% if entry.children %}
14
+ <div class="step-children" hidden>
15
+ {{ render_log_tree(entry.children, test_index, attachment_counter, assert_counter) }}
16
+ </div>
17
+ {% endif %}
10
18
  </li>
11
19
  {% elif entry.type == 'assert' %}
12
20
  {% set _ = assert_counter.update({'value': assert_counter.value + 1}) %}
@@ -10,8 +10,9 @@
10
10
  <header class="header">
11
11
  <div class="container">
12
12
  <div class="title">{{ title|e }}</div>
13
- <button id="theme-toggle" class="theme-toggle" title="Toggle theme">
14
- <span class="icon-dark">🌙</span><span class="icon-light">☀️</span>
13
+ <button id="theme-toggle" class="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
14
+ <svg class="theme-icon-moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
15
+ <svg class="theme-icon-sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
15
16
  </button>
16
17
  <section class="cards">
17
18
  <div class="card"><div class="label">Start</div><div class="value">{{ fmt_datetime(session_start) }}</div></div>
@@ -39,8 +40,23 @@
39
40
  <th>#</th>
40
41
  <th>Test</th>
41
42
  <th>Status</th>
42
- <th>Component</th>
43
- <th>Duration (s)</th>
43
+ <th>
44
+ <div class="th-filter-wrapper">
45
+ <span>Component</span>
46
+ <button class="th-filter-toggle" title="Filter Components">▾</button>
47
+ <div class="th-filter-menu" hidden>
48
+ <input type="text" class="th-filter-search" placeholder="Filter..." />
49
+ <label class="th-filter-item"><input type="checkbox" value="all" checked> <span>All</span></label>
50
+ <div class="th-filter-list"></div>
51
+ </div>
52
+ </div>
53
+ </th>
54
+ <th>
55
+ <div class="th-sort-wrapper" data-sort="duration">
56
+ <span>Duration (s)</span>
57
+ <span class="sort-icon">↕</span>
58
+ </div>
59
+ </th>
44
60
  </tr>
45
61
  </thead>
46
62
  <tbody>
@@ -49,7 +65,7 @@
49
65
  {% set test_index = loop.index %}
50
66
  {% set params_str = parse_params_from_nodeid(result.nodeid) %}
51
67
  {% set search_text = [result.nodeid, result.path, result.title, result.components|join(' ')]|join(' ')|e %}
52
- <tr class="test-row" data-status="{{ result.outcome }}" data-text="{{ search_text }}" role="button" tabindex="0" aria-expanded="false" aria-controls="{{ details_id }}">
68
+ <tr class="test-row" data-status="{{ result.outcome }}" data-text="{{ search_text }}" data-duration="{{ result.duration }}" role="button" tabindex="0" aria-expanded="false" aria-controls="{{ details_id }}">
53
69
  <td class="small">{{ loop.index }}</td>
54
70
  <td>
55
71
  <div class="mono">{{ decode_unicode_escapes(result.title)|e }}</div>
@@ -69,13 +85,20 @@
69
85
  <button class="details-btn copy-btn" type="button" data-copy="#grid-{{ details_id }}" title="Copy details"><span>📋</span> Copy</button>
70
86
  </div>
71
87
  <div class="grid" id="grid-{{ details_id }}">
72
- <div class="k">NodeID</div><div class="v mono">{{ decode_unicode_escapes(result.nodeid)|e }}</div>
88
+ <div class="k">NodeID</div>
89
+ <div class="v mono">{{ decode_unicode_escapes(result.nodeid)|e }}</div>
73
90
  <div class="k">File</div><div class="v mono">{{ result.path|e }}</div>
74
91
  {% if result.details.headline %}
75
92
  <div class="k">Result</div><div class="v"><span class="status {{ result.outcome }}">{{ result.details.headline|e }}</span></div>
76
93
  {% endif %}
77
94
  {% if result.execution_log %}
78
- <div class="k">Execution Log</div><div class="v mono">
95
+ <div class="k">
96
+ Execution Log
97
+ <div style="margin-top: 4px;">
98
+ <button class="details-btn step-toggle-all-btn" type="button" data-expanded="false" style="font-size: 10px; padding: 2px 6px;">Expand All</button>
99
+ </div>
100
+ </div>
101
+ <div class="v mono">
79
102
  {% import '_log_tree.html.jinja2' as log_helpers %}
80
103
  {{ log_helpers.render_log_tree(result.execution_log, test_index, {'value': 0}, {'value': 0}) }}
81
104
  </div>
@@ -96,25 +119,36 @@
96
119
  </div>
97
120
  </main>
98
121
 
99
- {# --- Modals --- #}
100
122
  {% for result in results %}
101
123
  {% set details_id = 'details-' ~ loop.index %}
102
124
  {% set test_index = loop.index %}
103
- {# Render modals for logs #}
104
125
  {% if result.details.captured_logs and result.details.captured_logs.strip() %}
105
126
  <div class="modal" id="logs-modal-{{ details_id }}" hidden>
106
127
  <div class="modal-overlay" data-modal-close></div>
107
128
  <div class="modal-content">
108
129
  <div class="modal-header">
109
130
  <h3 class="mono">{{ decode_unicode_escapes(result.title)|e }}</h3>
110
- <button class="modal-close" data-modal-close>&times;</button>
131
+ <div class="modal-actions">
132
+ <div class="attachment-controls" style="display: none;">
133
+ <input type="text" class="attachment-search" placeholder="Highlight text..." />
134
+ </div>
135
+ <div class="log-filter-wrapper">
136
+ <button class="details-btn log-filter-toggle">Filter Loggers ▾</button>
137
+ <div class="log-filter-menu" hidden>
138
+ <input type="text" class="log-filter-search" placeholder="Filter..." />
139
+ <label class="log-filter-item"><input type="checkbox" value="all" checked> <span>All Loggers</span></label>
140
+ <div class="log-filter-list"></div>
141
+ </div>
142
+ </div>
143
+ <button class="details-btn log-copy-btn" title="Copy visible logs"><span>📋</span> Copy</button>
144
+ <button class="modal-close" data-modal-close>&times;</button>
145
+ </div>
111
146
  </div>
112
147
  <div class="modal-body"><pre><code>{{ result.details.captured_logs|e }}</code></pre></div>
113
148
  </div>
114
149
  </div>
115
150
  {% endif %}
116
151
 
117
- {# Render modals for attachments #}
118
152
  {% for attachment in result.attachments %}
119
153
  {% set modal_id = 'attachment-modal-' ~ test_index ~ '-' ~ loop.index %}
120
154
  <div class="modal" id="{{ modal_id }}" hidden>
@@ -122,7 +156,14 @@
122
156
  <div class="modal-content">
123
157
  <div class="modal-header">
124
158
  <h3>{{ attachment.label|e }}</h3>
125
- <button class="modal-close" data-modal-close>&times;</button>
159
+ <div class="modal-actions">
160
+ <div class="attachment-controls" style="display: none;">
161
+ <input type="text" class="attachment-search" placeholder="Highlight text..." />
162
+ <button class="details-btn attachment-toggle-all-btn" title="Expand/Collapse All" data-expanded="true">Collapse All</button>
163
+ </div>
164
+ <button class="details-btn attachment-copy-btn" type="button" title="Copy content"><span>📋</span> Copy</button>
165
+ <button class="modal-close" data-modal-close>&times;</button>
166
+ </div>
126
167
  </div>
127
168
  <div class="modal-body">
128
169
  {% if attachment.content_type == "image" %}
@@ -136,6 +177,10 @@
136
177
  {% endfor %}
137
178
  {% endfor %}
138
179
 
180
+ <button id="scroll-to-top" class="scroll-to-top" title="Scroll to top" aria-label="Scroll to top">
181
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 15l-6-6-6 6"/></svg>
182
+ </button>
183
+
139
184
  <script>{{ js_content }}</script>
140
185
  </body>
141
186
  </html>