qa-insight 1.0.0__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 (35) hide show
  1. qa_insight-1.0.0/LICENSE +21 -0
  2. qa_insight-1.0.0/PKG-INFO +205 -0
  3. qa_insight-1.0.0/README.md +163 -0
  4. qa_insight-1.0.0/pyproject.toml +72 -0
  5. qa_insight-1.0.0/setup.cfg +4 -0
  6. qa_insight-1.0.0/src/qa_insight/__init__.py +18 -0
  7. qa_insight-1.0.0/src/qa_insight/behave_env.py +68 -0
  8. qa_insight-1.0.0/src/qa_insight/ci.py +68 -0
  9. qa_insight-1.0.0/src/qa_insight/client.py +172 -0
  10. qa_insight-1.0.0/src/qa_insight/config.py +75 -0
  11. qa_insight-1.0.0/src/qa_insight/dotenv.py +33 -0
  12. qa_insight-1.0.0/src/qa_insight/git.py +28 -0
  13. qa_insight-1.0.0/src/qa_insight/insight.py +233 -0
  14. qa_insight-1.0.0/src/qa_insight/junit_importer.py +115 -0
  15. qa_insight-1.0.0/src/qa_insight/pytest_plugin.py +119 -0
  16. qa_insight-1.0.0/src/qa_insight/redact.py +43 -0
  17. qa_insight-1.0.0/src/qa_insight/robot_library.py +43 -0
  18. qa_insight-1.0.0/src/qa_insight/robot_listener.py +70 -0
  19. qa_insight-1.0.0/src/qa_insight/synthesize.py +71 -0
  20. qa_insight-1.0.0/src/qa_insight/types.py +19 -0
  21. qa_insight-1.0.0/src/qa_insight.egg-info/PKG-INFO +205 -0
  22. qa_insight-1.0.0/src/qa_insight.egg-info/SOURCES.txt +33 -0
  23. qa_insight-1.0.0/src/qa_insight.egg-info/dependency_links.txt +1 -0
  24. qa_insight-1.0.0/src/qa_insight.egg-info/entry_points.txt +6 -0
  25. qa_insight-1.0.0/src/qa_insight.egg-info/requires.txt +22 -0
  26. qa_insight-1.0.0/src/qa_insight.egg-info/top_level.txt +1 -0
  27. qa_insight-1.0.0/tests/test_ci.py +54 -0
  28. qa_insight-1.0.0/tests/test_client.py +88 -0
  29. qa_insight-1.0.0/tests/test_config.py +45 -0
  30. qa_insight-1.0.0/tests/test_contract.py +78 -0
  31. qa_insight-1.0.0/tests/test_dotenv.py +43 -0
  32. qa_insight-1.0.0/tests/test_full_run.py +55 -0
  33. qa_insight-1.0.0/tests/test_golden.py +84 -0
  34. qa_insight-1.0.0/tests/test_integration.py +271 -0
  35. qa_insight-1.0.0/tests/test_redact.py +31 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 QA Insight
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: qa-insight
3
+ Version: 1.0.0
4
+ Summary: Official QA Insight SDK for Python. Sends test run events (pytest, Robot Framework, behave) to the QA Insight Collector without changing your tests.
5
+ Author-email: QA Insight <qainsight.io@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/QA-Insight/qa-insight
8
+ Project-URL: Repository, https://github.com/QA-Insight/qa-insight
9
+ Project-URL: Bug Tracker, https://github.com/QA-Insight/qa-insight/issues
10
+ Keywords: qa,testing,playwright,selenium,pytest,robot,behave,observability
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: pytest
25
+ Requires-Dist: pytest>=7.0; extra == "pytest"
26
+ Provides-Extra: robot
27
+ Requires-Dist: robotframework>=6.0; extra == "robot"
28
+ Provides-Extra: behave
29
+ Requires-Dist: behave>=1.2.6; extra == "behave"
30
+ Provides-Extra: examples
31
+ Requires-Dist: pytest>=7.0; extra == "examples"
32
+ Requires-Dist: pytest-playwright>=0.4; extra == "examples"
33
+ Requires-Dist: playwright>=1.30; extra == "examples"
34
+ Requires-Dist: selenium>=4.10; extra == "examples"
35
+ Requires-Dist: robotframework>=6.0; extra == "examples"
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=7.0; extra == "dev"
38
+ Requires-Dist: ruff>=0.4; extra == "dev"
39
+ Requires-Dist: build>=1.0; extra == "dev"
40
+ Requires-Dist: twine>=5.0; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # qa-insight (Python SDK)
44
+
45
+ SDK oficial de [QA Insight](https://github.com/QA-Insight/qa-insight) para **Python**.
46
+
47
+ Envía automáticamente el ciclo de vida de tus pruebas (inicio/fin de ejecución y de tests) al **Collector** de QA Insight, sin modificar tus tests. Compatible con **pytest** (incluido Selenium WebDriver 4 y Playwright), **Robot Framework**, **behave** (BDD) y un **importador de JUnit XML** post-run.
48
+
49
+ - **Cero dependencias runtime** (solo stdlib: `urllib`, `threading`, `queue`, `subprocess`, `re`). Las integraciones (pytest, robotframework, behave) son dependencias opcionales.
50
+ - Python **3.9+**.
51
+ - Nunca altera el resultado de tus pruebas; si el Collector no responde, registra una advertencia y continúa.
52
+
53
+ ## Instalación
54
+
55
+ ```bash
56
+ pip install -e . # desde el repo (desarrollo)
57
+ pip install -e ".[examples]" # con pytest + Playwright + Selenium + Robot para los ejemplos
58
+ ```
59
+
60
+ ## Configuración por variables de entorno
61
+
62
+ | Variable | Default | Descripción |
63
+ |---|---|---|
64
+ | `QAI_ENDPOINT` | `http://localhost:3000/collector/v1/events` | URL del Collector de QA Insight |
65
+ | `QAI_API_KEY` | — | API key de QA Insight (proyecto + ambiente) |
66
+ | `QAI_ENABLED` | `true` | `false` desactiva el envío |
67
+ | `QAI_TIMEOUT_MS` | `2000` | Timeout por request HTTP |
68
+ | `QAI_RETRY_MAX` | `3` | Reintentos para errores transitorios (5xx, 429, red) |
69
+ | `QAI_FLUSH_TIMEOUT_MS` | `5000` | Máximo de espera del flush al terminar |
70
+ | `QAI_RUN_NAME` | `python-run-<ts>` | Nombre de la ejecución |
71
+ | `QAI_RUN_ID` | UUID generado | `externalRunId` (idempotencia del run) |
72
+ | `QAI_FRAMEWORK` | `pytest`/`robot`/`behave`/`junit` | Framework reportado a la columna framework |
73
+ | `QAI_BROWSER` | — | Browser/proyecto (chromium, firefox, webkit, chrome, ...) |
74
+
75
+ ## pytest
76
+
77
+ El plugin se auto-registra (entry point `pytest11`). Solo necesitas las variables de entorno:
78
+
79
+ ```bash
80
+ export QAI_ENDPOINT=http://localhost:3000/collector/v1/events
81
+ export QAI_API_KEY=qai_...
82
+ export QAI_FRAMEWORK=playwright # o selenium, o pytest
83
+ pytest
84
+ ```
85
+
86
+ - `pytest_sessionstart` → `RUN_STARTED`
87
+ - Cada test → `TEST_STARTED` / `TEST_FINISHED` (con `retryIndex` si usás `pytest-rerunfailures`)
88
+ - `pytest_sessionfinish` → `RUN_FINISHED`
89
+ - Los tests parametrizados generan nodeids únicos → no colapsan en el backend.
90
+
91
+ Para reportar el browser por test (badges de browser en la UI), setealos en un `conftest.py`:
92
+
93
+ ```python
94
+ # conftest.py (Playwright)
95
+ import pytest
96
+ from qa_insight import QaiContext
97
+
98
+ @pytest.fixture(autouse=True)
99
+ def qai_browser(browser):
100
+ QaiContext.set_browser(browser.browser_type.name)
101
+ ```
102
+
103
+ ```python
104
+ # conftest.py (Selenium 4)
105
+ import pytest
106
+ from qa_insight import QaiContext
107
+
108
+ @pytest.fixture
109
+ def driver():
110
+ from selenium import webdriver
111
+ d = webdriver.Chrome() # Selenium Manager descarga el driver automáticamente
112
+ QaiContext.set_webdriver(d)
113
+ yield d
114
+ d.quit()
115
+ ```
116
+
117
+ El plugin resuelve el browser en este orden: `QaiContext.browser` → `QAI_BROWSER` → capabilities del `QaiContext.webdriver`.
118
+
119
+ ### Cross-browser
120
+
121
+ - **Playwright**: `pytest --browser chromium --browser firefox --browser webkit` (cada browser genera su ejecución con sus badges).
122
+ - **Selenium 4**: `pytest --selenium-browser chrome` / `pytest --selenium-browser firefox` (ver ejemplo).
123
+ - **Robot Framework**: exporta `QAI_BROWSER=chrome|firefox` al correr con `--variable BROWSER:...`.
124
+
125
+ ## Robot Framework
126
+
127
+ El SDK incluye un **library listener** (`QaiInsightLibrary`) que se auto-registra, de modo que no hace falta
128
+ pasar `--listener` en la línea de comandos. Solo importá la librería (idealmente en un resource compartido):
129
+
130
+ ```robotframework
131
+ *** Settings ***
132
+ Library qa_insight.robot_library.QaiInsightLibrary
133
+ ```
134
+
135
+ Al importarse, la librería carga el `.env` del proyecto, emite `RUN_STARTED`, registra el listener para
136
+ toda la ejecución (tags, suites, tests específicos o archivos individuales) y, al terminar, emite
137
+ `RUN_FINISHED` de forma garantizada vía su método `close()`.
138
+
139
+ Configuración vía `.env` (sin necesidad de `export`):
140
+
141
+ ```bash
142
+ QAI_API_KEY=qai_...
143
+ QAI_ENDPOINT=http://localhost:3000/collector/v1/events
144
+ QAI_FRAMEWORK=robot
145
+ QAI_BROWSER=chrome # opcional: badges de browser + Open Browser de SeleniumLibrary
146
+ HEADLESS=True # opcional: modo headless/visible del navegador
147
+ ```
148
+
149
+ Ejecutá tus tests como siempre:
150
+
151
+ ```bash
152
+ robot tests/ # todo
153
+ robot --include smoke tests/ # por tags
154
+ robot --suite my_suite tests/ # por suite
155
+ robot --test "my test" tests/ # por test
156
+ ```
157
+
158
+ > Alternativa (CLI listener): `robot --listener qa_insight.robot_listener.QaiInsightListener tests/`
159
+
160
+ ## behave (BDD)
161
+
162
+ ```python
163
+ # features/environment.py
164
+ from qa_insight.behave_env import * # noqa
165
+ ```
166
+
167
+ ## Importador de JUnit XML (post-run)
168
+
169
+ ```bash
170
+ export QAI_API_KEY=qai_...
171
+ qa-insight-import junit.xml --framework pytest
172
+ # o para cualquier framework que emita JUnit XML
173
+ ```
174
+
175
+ ## Uso manual / sin runner
176
+
177
+ ```python
178
+ from qa_insight import QaiInsight
179
+
180
+ insight = QaiInsight({"api_key": "qai_...", "framework": "selenium"})
181
+ insight.start_run()
182
+ insight.start_test("test_login", suite="tests/test_auth.py")
183
+ insight.finish_test("test_login", suite="tests/test_auth.py", status="PASSED", duration_ms=1200)
184
+ insight.finish_run()
185
+ ```
186
+
187
+ ## Comportamiento ante fallos de red
188
+
189
+ - Buffer no bloqueante en un hilo de fondo + flush acotado (`QAI_FLUSH_TIMEOUT_MS`) al terminar.
190
+ - Errores transitorios (timeout, red, 5xx, 429) se reintentan con backoff exponencial (50 ms·2^n, cap 1 s).
191
+ - Errores permanentes (4xx) no se reintentan.
192
+ - La API key **nunca** se imprime ni se incluye en los payloads; `errorMessage`/`stacktrace` se redactan antes de enviar (authorization, bearer, cookie, password, secret, api-key, token, `qai_`, `xox*` + literales custom).
193
+ - Payload limitado a 100 KB (se trunca el stacktrace si excede).
194
+
195
+ ## Desarrollo
196
+
197
+ ```bash
198
+ pip install -e ".[dev]"
199
+ ruff check src tests
200
+ pytest
201
+ ```
202
+
203
+ ## Licencia
204
+
205
+ MIT
@@ -0,0 +1,163 @@
1
+ # qa-insight (Python SDK)
2
+
3
+ SDK oficial de [QA Insight](https://github.com/QA-Insight/qa-insight) para **Python**.
4
+
5
+ Envía automáticamente el ciclo de vida de tus pruebas (inicio/fin de ejecución y de tests) al **Collector** de QA Insight, sin modificar tus tests. Compatible con **pytest** (incluido Selenium WebDriver 4 y Playwright), **Robot Framework**, **behave** (BDD) y un **importador de JUnit XML** post-run.
6
+
7
+ - **Cero dependencias runtime** (solo stdlib: `urllib`, `threading`, `queue`, `subprocess`, `re`). Las integraciones (pytest, robotframework, behave) son dependencias opcionales.
8
+ - Python **3.9+**.
9
+ - Nunca altera el resultado de tus pruebas; si el Collector no responde, registra una advertencia y continúa.
10
+
11
+ ## Instalación
12
+
13
+ ```bash
14
+ pip install -e . # desde el repo (desarrollo)
15
+ pip install -e ".[examples]" # con pytest + Playwright + Selenium + Robot para los ejemplos
16
+ ```
17
+
18
+ ## Configuración por variables de entorno
19
+
20
+ | Variable | Default | Descripción |
21
+ |---|---|---|
22
+ | `QAI_ENDPOINT` | `http://localhost:3000/collector/v1/events` | URL del Collector de QA Insight |
23
+ | `QAI_API_KEY` | — | API key de QA Insight (proyecto + ambiente) |
24
+ | `QAI_ENABLED` | `true` | `false` desactiva el envío |
25
+ | `QAI_TIMEOUT_MS` | `2000` | Timeout por request HTTP |
26
+ | `QAI_RETRY_MAX` | `3` | Reintentos para errores transitorios (5xx, 429, red) |
27
+ | `QAI_FLUSH_TIMEOUT_MS` | `5000` | Máximo de espera del flush al terminar |
28
+ | `QAI_RUN_NAME` | `python-run-<ts>` | Nombre de la ejecución |
29
+ | `QAI_RUN_ID` | UUID generado | `externalRunId` (idempotencia del run) |
30
+ | `QAI_FRAMEWORK` | `pytest`/`robot`/`behave`/`junit` | Framework reportado a la columna framework |
31
+ | `QAI_BROWSER` | — | Browser/proyecto (chromium, firefox, webkit, chrome, ...) |
32
+
33
+ ## pytest
34
+
35
+ El plugin se auto-registra (entry point `pytest11`). Solo necesitas las variables de entorno:
36
+
37
+ ```bash
38
+ export QAI_ENDPOINT=http://localhost:3000/collector/v1/events
39
+ export QAI_API_KEY=qai_...
40
+ export QAI_FRAMEWORK=playwright # o selenium, o pytest
41
+ pytest
42
+ ```
43
+
44
+ - `pytest_sessionstart` → `RUN_STARTED`
45
+ - Cada test → `TEST_STARTED` / `TEST_FINISHED` (con `retryIndex` si usás `pytest-rerunfailures`)
46
+ - `pytest_sessionfinish` → `RUN_FINISHED`
47
+ - Los tests parametrizados generan nodeids únicos → no colapsan en el backend.
48
+
49
+ Para reportar el browser por test (badges de browser en la UI), setealos en un `conftest.py`:
50
+
51
+ ```python
52
+ # conftest.py (Playwright)
53
+ import pytest
54
+ from qa_insight import QaiContext
55
+
56
+ @pytest.fixture(autouse=True)
57
+ def qai_browser(browser):
58
+ QaiContext.set_browser(browser.browser_type.name)
59
+ ```
60
+
61
+ ```python
62
+ # conftest.py (Selenium 4)
63
+ import pytest
64
+ from qa_insight import QaiContext
65
+
66
+ @pytest.fixture
67
+ def driver():
68
+ from selenium import webdriver
69
+ d = webdriver.Chrome() # Selenium Manager descarga el driver automáticamente
70
+ QaiContext.set_webdriver(d)
71
+ yield d
72
+ d.quit()
73
+ ```
74
+
75
+ El plugin resuelve el browser en este orden: `QaiContext.browser` → `QAI_BROWSER` → capabilities del `QaiContext.webdriver`.
76
+
77
+ ### Cross-browser
78
+
79
+ - **Playwright**: `pytest --browser chromium --browser firefox --browser webkit` (cada browser genera su ejecución con sus badges).
80
+ - **Selenium 4**: `pytest --selenium-browser chrome` / `pytest --selenium-browser firefox` (ver ejemplo).
81
+ - **Robot Framework**: exporta `QAI_BROWSER=chrome|firefox` al correr con `--variable BROWSER:...`.
82
+
83
+ ## Robot Framework
84
+
85
+ El SDK incluye un **library listener** (`QaiInsightLibrary`) que se auto-registra, de modo que no hace falta
86
+ pasar `--listener` en la línea de comandos. Solo importá la librería (idealmente en un resource compartido):
87
+
88
+ ```robotframework
89
+ *** Settings ***
90
+ Library qa_insight.robot_library.QaiInsightLibrary
91
+ ```
92
+
93
+ Al importarse, la librería carga el `.env` del proyecto, emite `RUN_STARTED`, registra el listener para
94
+ toda la ejecución (tags, suites, tests específicos o archivos individuales) y, al terminar, emite
95
+ `RUN_FINISHED` de forma garantizada vía su método `close()`.
96
+
97
+ Configuración vía `.env` (sin necesidad de `export`):
98
+
99
+ ```bash
100
+ QAI_API_KEY=qai_...
101
+ QAI_ENDPOINT=http://localhost:3000/collector/v1/events
102
+ QAI_FRAMEWORK=robot
103
+ QAI_BROWSER=chrome # opcional: badges de browser + Open Browser de SeleniumLibrary
104
+ HEADLESS=True # opcional: modo headless/visible del navegador
105
+ ```
106
+
107
+ Ejecutá tus tests como siempre:
108
+
109
+ ```bash
110
+ robot tests/ # todo
111
+ robot --include smoke tests/ # por tags
112
+ robot --suite my_suite tests/ # por suite
113
+ robot --test "my test" tests/ # por test
114
+ ```
115
+
116
+ > Alternativa (CLI listener): `robot --listener qa_insight.robot_listener.QaiInsightListener tests/`
117
+
118
+ ## behave (BDD)
119
+
120
+ ```python
121
+ # features/environment.py
122
+ from qa_insight.behave_env import * # noqa
123
+ ```
124
+
125
+ ## Importador de JUnit XML (post-run)
126
+
127
+ ```bash
128
+ export QAI_API_KEY=qai_...
129
+ qa-insight-import junit.xml --framework pytest
130
+ # o para cualquier framework que emita JUnit XML
131
+ ```
132
+
133
+ ## Uso manual / sin runner
134
+
135
+ ```python
136
+ from qa_insight import QaiInsight
137
+
138
+ insight = QaiInsight({"api_key": "qai_...", "framework": "selenium"})
139
+ insight.start_run()
140
+ insight.start_test("test_login", suite="tests/test_auth.py")
141
+ insight.finish_test("test_login", suite="tests/test_auth.py", status="PASSED", duration_ms=1200)
142
+ insight.finish_run()
143
+ ```
144
+
145
+ ## Comportamiento ante fallos de red
146
+
147
+ - Buffer no bloqueante en un hilo de fondo + flush acotado (`QAI_FLUSH_TIMEOUT_MS`) al terminar.
148
+ - Errores transitorios (timeout, red, 5xx, 429) se reintentan con backoff exponencial (50 ms·2^n, cap 1 s).
149
+ - Errores permanentes (4xx) no se reintentan.
150
+ - La API key **nunca** se imprime ni se incluye en los payloads; `errorMessage`/`stacktrace` se redactan antes de enviar (authorization, bearer, cookie, password, secret, api-key, token, `qai_`, `xox*` + literales custom).
151
+ - Payload limitado a 100 KB (se trunca el stacktrace si excede).
152
+
153
+ ## Desarrollo
154
+
155
+ ```bash
156
+ pip install -e ".[dev]"
157
+ ruff check src tests
158
+ pytest
159
+ ```
160
+
161
+ ## Licencia
162
+
163
+ MIT
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "qa-insight"
7
+ version = "1.0.0"
8
+ description = "Official QA Insight SDK for Python. Sends test run events (pytest, Robot Framework, behave) to the QA Insight Collector without changing your tests."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "QA Insight", email = "qainsight.io@gmail.com" }]
14
+ keywords = ["qa", "testing", "playwright", "selenium", "pytest", "robot", "behave", "observability"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Quality Assurance",
25
+ "Topic :: Software Development :: Testing",
26
+ ]
27
+ dependencies = []
28
+
29
+ [project.optional-dependencies]
30
+ pytest = ["pytest>=7.0"]
31
+ robot = ["robotframework>=6.0"]
32
+ behave = ["behave>=1.2.6"]
33
+ examples = [
34
+ "pytest>=7.0",
35
+ "pytest-playwright>=0.4",
36
+ "playwright>=1.30",
37
+ "selenium>=4.10",
38
+ "robotframework>=6.0",
39
+ ]
40
+ dev = [
41
+ "pytest>=7.0",
42
+ "ruff>=0.4",
43
+ "build>=1.0",
44
+ "twine>=5.0",
45
+ ]
46
+
47
+ [project.urls]
48
+ Homepage = "https://github.com/QA-Insight/qa-insight"
49
+ Repository = "https://github.com/QA-Insight/qa-insight"
50
+ "Bug Tracker" = "https://github.com/QA-Insight/qa-insight/issues"
51
+
52
+ [project.entry-points.pytest11]
53
+ qa-insight = "qa_insight.pytest_plugin"
54
+
55
+ [project.scripts]
56
+ qa-insight-import = "qa_insight.junit_importer:main"
57
+ qa-insight-synthesize = "qa_insight.synthesize:main"
58
+
59
+ [tool.setuptools.packages.find]
60
+ where = ["src"]
61
+
62
+ [tool.ruff]
63
+ line-length = 110
64
+ target-version = "py39"
65
+
66
+ [tool.ruff.lint]
67
+ select = ["E", "F", "W", "I", "UP"]
68
+ ignore = ["E501"]
69
+
70
+ [tool.pytest.ini_options]
71
+ testpaths = ["tests"]
72
+ addopts = "-q"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,18 @@
1
+ from .client import QaiClient
2
+ from .config import QaiConfig, resolve_config
3
+ from .dotenv import load_dotenv
4
+ from .insight import QaiContext, QaiInsight
5
+ from .redact import build_redactor
6
+ from .robot_library import QaiInsightLibrary
7
+
8
+ __all__ = [
9
+ "QaiClient",
10
+ "QaiConfig",
11
+ "QaiContext",
12
+ "QaiInsight",
13
+ "QaiInsightLibrary",
14
+ "build_redactor",
15
+ "load_dotenv",
16
+ "resolve_config",
17
+ ]
18
+ __version__ = "1.0.0"
@@ -0,0 +1,68 @@
1
+ """behave hooks.
2
+
3
+ Import this module from your ``environment.py``:
4
+
5
+ from qa_insight.behave_env import * # noqa
6
+
7
+ or copy the hooks into your own ``environment.py``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import time
13
+
14
+ from .insight import QaiInsight
15
+ from .types import QaiTestStatus
16
+
17
+ _DEFAULT_FRAMEWORK = "behave"
18
+
19
+ _insight: QaiInsight | None = None
20
+
21
+ _STATUS = {
22
+ "passed": QaiTestStatus.PASSED.value,
23
+ "failed": QaiTestStatus.FAILED.value,
24
+ "skipped": QaiTestStatus.SKIPPED.value,
25
+ }
26
+
27
+ _started_at: float | None = None
28
+
29
+
30
+ def before_all(context) -> None:
31
+ global _insight
32
+ _insight = QaiInsight({"framework": _DEFAULT_FRAMEWORK})
33
+ _insight.start_run()
34
+
35
+
36
+ def before_scenario(context, scenario) -> None:
37
+ global _insight, _started_at
38
+ if _insight is None:
39
+ return
40
+ feature = getattr(scenario, "feature", None)
41
+ _started_at = time.time()
42
+ _insight.start_test(scenario.name, suite=getattr(feature, "name", None) or None, started_at=_started_at)
43
+
44
+
45
+ def after_scenario(context, scenario) -> None:
46
+ global _insight
47
+ if _insight is None:
48
+ return
49
+ feature = getattr(scenario, "feature", None)
50
+ duration_ms = int((getattr(scenario, "duration", 0) or 0) * 1000)
51
+ # behave >= 1.3 exposes scenario.status as an IntEnum (Status.passed);
52
+ # fall back to a plain string for older versions.
53
+ status_value = getattr(scenario.status, "name", None) or scenario.status
54
+ status = _STATUS.get(status_value, QaiTestStatus.ABORTED.value)
55
+ _insight.finish_test(
56
+ scenario.name,
57
+ suite=getattr(feature, "name", None) or None,
58
+ status=status,
59
+ duration_ms=duration_ms,
60
+ started_at=_started_at,
61
+ )
62
+
63
+
64
+ def after_all(context) -> None:
65
+ global _insight
66
+ if _insight is not None:
67
+ _insight.finish_run()
68
+ _insight = None
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+
6
+ def _env(name: str) -> str | None:
7
+ value = os.environ.get(name)
8
+ return value if value and len(value) > 0 else None
9
+
10
+
11
+ def _github_actions() -> dict[str, str | None]:
12
+ server = _env("GITHUB_SERVER_URL") or "https://github.com"
13
+ repo = _env("GITHUB_REPOSITORY")
14
+ run_id = _env("GITHUB_RUN_ID")
15
+ pipeline_url = f"{server}/{repo}/actions/runs/{run_id}" if repo and run_id else None
16
+ return {
17
+ "provider": "github-actions",
18
+ "branch": _env("GITHUB_HEAD_REF") or _env("GITHUB_REF_NAME"),
19
+ "commitSha": _env("GITHUB_SHA"),
20
+ "pipelineUrl": pipeline_url,
21
+ "runId": run_id,
22
+ "buildId": _env("GITHUB_RUN_ATTEMPT"),
23
+ }
24
+
25
+
26
+ def _gitlab_ci() -> dict[str, str | None]:
27
+ return {
28
+ "provider": "gitlab-ci",
29
+ "branch": _env("CI_COMMIT_BRANCH") or _env("CI_COMMIT_REF_NAME"),
30
+ "commitSha": _env("CI_COMMIT_SHA"),
31
+ "pipelineUrl": _env("CI_PIPELINE_URL") or _env("CI_JOB_URL"),
32
+ "runId": _env("CI_PIPELINE_ID") or _env("CI_JOB_ID"),
33
+ "buildId": _env("CI_JOB_ID"),
34
+ }
35
+
36
+
37
+ def _jenkins() -> dict[str, str | None]:
38
+ return {
39
+ "provider": "jenkins",
40
+ "branch": _env("BRANCH_NAME") or _env("GIT_BRANCH"),
41
+ "commitSha": _env("GIT_COMMIT"),
42
+ "pipelineUrl": _env("BUILD_URL"),
43
+ "runId": _env("BUILD_NUMBER"),
44
+ "buildId": _env("BUILD_ID") or _env("BUILD_NUMBER"),
45
+ }
46
+
47
+
48
+ def _azure_devops() -> dict[str, str | None]:
49
+ return {
50
+ "provider": "azure-pipelines",
51
+ "branch": _env("BUILD_SOURCEBRANCH"),
52
+ "commitSha": _env("BUILD_SOURCEVERSION"),
53
+ "pipelineUrl": _env("BUILD_BUILDURI"),
54
+ "runId": _env("BUILD_BUILDID"),
55
+ "buildId": _env("BUILD_BUILDID"),
56
+ }
57
+
58
+
59
+ def detect_ci() -> dict[str, str | None]:
60
+ if _env("GITHUB_ACTIONS") == "true":
61
+ return _github_actions()
62
+ if _env("GITLAB_CI") == "true":
63
+ return _gitlab_ci()
64
+ if _env("JENKINS_URL") or _env("JENKINS_HOME"):
65
+ return _jenkins()
66
+ if _env("TF_BUILD") in ("True", "true"):
67
+ return _azure_devops()
68
+ return {}