looper-dashboard 0.1.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.
- looper_dashboard-0.1.0/PKG-INFO +198 -0
- looper_dashboard-0.1.0/README.md +187 -0
- looper_dashboard-0.1.0/looper_dashboard/__init__.py +3 -0
- looper_dashboard-0.1.0/looper_dashboard/ai_analyzer.py +221 -0
- looper_dashboard-0.1.0/looper_dashboard/cli.py +282 -0
- looper_dashboard-0.1.0/looper_dashboard/karate_generator.py +279 -0
- looper_dashboard-0.1.0/looper_dashboard/karate_ops.py +286 -0
- looper_dashboard-0.1.0/looper_dashboard/looper_auth.py +186 -0
- looper_dashboard-0.1.0/looper_dashboard/looper_core.py +196 -0
- looper_dashboard-0.1.0/looper_dashboard/monocart_generator.py +251 -0
- looper_dashboard-0.1.0/looper_dashboard/monocart_ops.py +287 -0
- looper_dashboard-0.1.0/looper_dashboard/orchestrator.py +263 -0
- looper_dashboard-0.1.0/looper_dashboard/playwright_generator.py +203 -0
- looper_dashboard-0.1.0/looper_dashboard/playwright_ops.py +270 -0
- looper_dashboard-0.1.0/looper_dashboard/templates/__init__.py +0 -0
- looper_dashboard-0.1.0/looper_dashboard/templates/karate.html +575 -0
- looper_dashboard-0.1.0/looper_dashboard/templates/monocart.html +578 -0
- looper_dashboard-0.1.0/looper_dashboard/templates/playwright.html +577 -0
- looper_dashboard-0.1.0/looper_dashboard.egg-info/PKG-INFO +198 -0
- looper_dashboard-0.1.0/looper_dashboard.egg-info/SOURCES.txt +24 -0
- looper_dashboard-0.1.0/looper_dashboard.egg-info/dependency_links.txt +1 -0
- looper_dashboard-0.1.0/looper_dashboard.egg-info/entry_points.txt +2 -0
- looper_dashboard-0.1.0/looper_dashboard.egg-info/requires.txt +2 -0
- looper_dashboard-0.1.0/looper_dashboard.egg-info/top_level.txt +1 -0
- looper_dashboard-0.1.0/pyproject.toml +26 -0
- looper_dashboard-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: looper-dashboard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate daily CI dashboards for any QE repo on LooperPro (Karate, Playwright, Monocart)
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: karate,playwright,monocart,looperpro,ci,dashboard
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pyyaml>=6.0
|
|
10
|
+
Requires-Dist: requests>=2.31
|
|
11
|
+
|
|
12
|
+
# looper-dashboard
|
|
13
|
+
|
|
14
|
+
Generate daily CI dashboards for any QE repository on the [LooperPro](https://looperpro.prod.walmart.com) CI platform.
|
|
15
|
+
|
|
16
|
+
Supports three test suite types:
|
|
17
|
+
|
|
18
|
+
| Suite | Report format | Default history |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `karate` | `karate-summary-json.txt` + per-feature detail | 14 days |
|
|
21
|
+
| `playwright` | `pw-summary-report.json` | 14 days |
|
|
22
|
+
| `playwright-monocart` | `monocart-report/index.html` (zlib-compressed) | 30 days |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
| Requirement | Why |
|
|
29
|
+
|---|---|
|
|
30
|
+
| Python 3.11+ | Runtime |
|
|
31
|
+
| `mcp-cli` (optional) | Auto-refreshes LooperPro auth tokens |
|
|
32
|
+
| `code-puppy` (optional) | Powers the AI failure analysis step |
|
|
33
|
+
|
|
34
|
+
Authentication is read from `~/.mcp-cli/tokens.json` or `~/.code_puppy/puppy.cfg` automatically — no extra config needed.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install looper-dashboard
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or from the repo:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install ./looper-dashboard
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
### 1. Scaffold a config
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Creates daily_flows.yml in the current directory
|
|
58
|
+
looper-dashboard --init karate
|
|
59
|
+
looper-dashboard --init playwright
|
|
60
|
+
looper-dashboard --init playwright-monocart
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Edit the generated file to add your LooperPro job names and flow names.
|
|
64
|
+
|
|
65
|
+
### 2. Generate the dashboard
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
looper-dashboard --suite karate --config daily_flows.yml --open
|
|
69
|
+
looper-dashboard --suite playwright --config daily_flows.yml --open
|
|
70
|
+
looper-dashboard --suite playwright-monocart --config daily_flows.yml --open
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Override the repo SCM URL
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
looper-dashboard --suite karate \
|
|
77
|
+
--config daily_flows.yml \
|
|
78
|
+
--scm-url https://gecgithub01.walmart.com/my-org/my-repo.git \
|
|
79
|
+
--open
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## `daily_flows.yml` format
|
|
85
|
+
|
|
86
|
+
### Karate / Playwright
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
# SCM URL of the repository being monitored
|
|
90
|
+
scm_url: https://gecgithub01.walmart.com/my-org/my-repo.git
|
|
91
|
+
|
|
92
|
+
jobs:
|
|
93
|
+
|
|
94
|
+
# LooperPro job name → list of flow names to monitor
|
|
95
|
+
my-org/my-repo-tests:
|
|
96
|
+
- my-flow-dev
|
|
97
|
+
- my-flow-stage
|
|
98
|
+
- my-flow-prod
|
|
99
|
+
|
|
100
|
+
my-org/my-repo-health-checks:
|
|
101
|
+
- health-dev
|
|
102
|
+
- health-stage
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Playwright-Monocart (with explicit job_ids)
|
|
106
|
+
|
|
107
|
+
Some repos' jobs cannot be discovered by SCM URL in LooperPro. Add a `job_ids` section with the UUIDs from the LooperPro UI:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
scm_url: https://gecgithub01.walmart.com/my-org/my-repo.git
|
|
111
|
+
|
|
112
|
+
# Explicit job IDs (look these up in the LooperPro UI)
|
|
113
|
+
job_ids:
|
|
114
|
+
my-monocart-job: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
115
|
+
|
|
116
|
+
jobs:
|
|
117
|
+
|
|
118
|
+
my-monocart-job:
|
|
119
|
+
- e2e-flow-dev
|
|
120
|
+
- e2e-flow-stage
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## CLI Reference
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
looper-dashboard [OPTIONS]
|
|
129
|
+
|
|
130
|
+
Options:
|
|
131
|
+
--suite SUITE Test suite type: karate, playwright, playwright-monocart [required]
|
|
132
|
+
--config PATH Path to daily_flows.yml [required]
|
|
133
|
+
--scm-url URL Override scm_url from daily_flows.yml
|
|
134
|
+
--output PATH Output HTML file path (default varies by suite)
|
|
135
|
+
--days N Days of build history to fetch (default: 14 or 30)
|
|
136
|
+
--open Auto-open dashboard in browser when done
|
|
137
|
+
--no-ai Skip the AI failure analysis step
|
|
138
|
+
--no-details Skip fetching test summary details (faster, status only)
|
|
139
|
+
--save-failures PATH Path to save failures JSON
|
|
140
|
+
--analysis-json PATH Use a pre-computed AI analysis JSON instead of running live AI
|
|
141
|
+
--init SUITE Scaffold a starter daily_flows.yml and exit
|
|
142
|
+
|
|
143
|
+
-h, --help Show help and exit
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## AI Analysis
|
|
149
|
+
|
|
150
|
+
When `--no-ai` is not set, the orchestrator:
|
|
151
|
+
|
|
152
|
+
1. Saves a `*-failures.json` summary of all failing flows
|
|
153
|
+
2. Invokes `code-puppy` to classify each failure as one of:
|
|
154
|
+
- `service_regression` — likely a code regression in the app
|
|
155
|
+
- `environment_issue` — infra/network/auth failure
|
|
156
|
+
- `test_issue` — stale selector or bad test data
|
|
157
|
+
- `flaky` — intermittent, no clear pattern
|
|
158
|
+
3. Regenerates the dashboard HTML with an AI analysis panel embedded
|
|
159
|
+
|
|
160
|
+
If `code-puppy` is not installed, the AI step is skipped gracefully.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Publishing
|
|
165
|
+
|
|
166
|
+
### To public PyPI
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
cd looper-dashboard
|
|
170
|
+
pip install hatch
|
|
171
|
+
hatch build # creates dist/looper_dashboard-0.1.0.tar.gz and .whl
|
|
172
|
+
hatch publish # prompts for PyPI API token
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### To Walmart internal PyPI registry
|
|
176
|
+
|
|
177
|
+
Add the registry to `~/.config/hatch/config.toml`:
|
|
178
|
+
|
|
179
|
+
```toml
|
|
180
|
+
[publish.index.repos.internal]
|
|
181
|
+
url = "https://your-internal-pypi-registry/simple/"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Then:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
hatch publish --repo internal
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Onboarding a new repo
|
|
193
|
+
|
|
194
|
+
1. Create a `daily_flows.yml` for the target repo (use `--init` to scaffold)
|
|
195
|
+
2. Add job and flow names (find them in LooperPro or via `mcp-cli`)
|
|
196
|
+
3. Run `looper-dashboard --suite <type> --config daily_flows.yml --open`
|
|
197
|
+
|
|
198
|
+
No code changes required — the package is entirely configuration-driven.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# looper-dashboard
|
|
2
|
+
|
|
3
|
+
Generate daily CI dashboards for any QE repository on the [LooperPro](https://looperpro.prod.walmart.com) CI platform.
|
|
4
|
+
|
|
5
|
+
Supports three test suite types:
|
|
6
|
+
|
|
7
|
+
| Suite | Report format | Default history |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `karate` | `karate-summary-json.txt` + per-feature detail | 14 days |
|
|
10
|
+
| `playwright` | `pw-summary-report.json` | 14 days |
|
|
11
|
+
| `playwright-monocart` | `monocart-report/index.html` (zlib-compressed) | 30 days |
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
| Requirement | Why |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Python 3.11+ | Runtime |
|
|
20
|
+
| `mcp-cli` (optional) | Auto-refreshes LooperPro auth tokens |
|
|
21
|
+
| `code-puppy` (optional) | Powers the AI failure analysis step |
|
|
22
|
+
|
|
23
|
+
Authentication is read from `~/.mcp-cli/tokens.json` or `~/.code_puppy/puppy.cfg` automatically — no extra config needed.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install looper-dashboard
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or from the repo:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install ./looper-dashboard
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### 1. Scaffold a config
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Creates daily_flows.yml in the current directory
|
|
47
|
+
looper-dashboard --init karate
|
|
48
|
+
looper-dashboard --init playwright
|
|
49
|
+
looper-dashboard --init playwright-monocart
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Edit the generated file to add your LooperPro job names and flow names.
|
|
53
|
+
|
|
54
|
+
### 2. Generate the dashboard
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
looper-dashboard --suite karate --config daily_flows.yml --open
|
|
58
|
+
looper-dashboard --suite playwright --config daily_flows.yml --open
|
|
59
|
+
looper-dashboard --suite playwright-monocart --config daily_flows.yml --open
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Override the repo SCM URL
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
looper-dashboard --suite karate \
|
|
66
|
+
--config daily_flows.yml \
|
|
67
|
+
--scm-url https://gecgithub01.walmart.com/my-org/my-repo.git \
|
|
68
|
+
--open
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## `daily_flows.yml` format
|
|
74
|
+
|
|
75
|
+
### Karate / Playwright
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
# SCM URL of the repository being monitored
|
|
79
|
+
scm_url: https://gecgithub01.walmart.com/my-org/my-repo.git
|
|
80
|
+
|
|
81
|
+
jobs:
|
|
82
|
+
|
|
83
|
+
# LooperPro job name → list of flow names to monitor
|
|
84
|
+
my-org/my-repo-tests:
|
|
85
|
+
- my-flow-dev
|
|
86
|
+
- my-flow-stage
|
|
87
|
+
- my-flow-prod
|
|
88
|
+
|
|
89
|
+
my-org/my-repo-health-checks:
|
|
90
|
+
- health-dev
|
|
91
|
+
- health-stage
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Playwright-Monocart (with explicit job_ids)
|
|
95
|
+
|
|
96
|
+
Some repos' jobs cannot be discovered by SCM URL in LooperPro. Add a `job_ids` section with the UUIDs from the LooperPro UI:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
scm_url: https://gecgithub01.walmart.com/my-org/my-repo.git
|
|
100
|
+
|
|
101
|
+
# Explicit job IDs (look these up in the LooperPro UI)
|
|
102
|
+
job_ids:
|
|
103
|
+
my-monocart-job: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
104
|
+
|
|
105
|
+
jobs:
|
|
106
|
+
|
|
107
|
+
my-monocart-job:
|
|
108
|
+
- e2e-flow-dev
|
|
109
|
+
- e2e-flow-stage
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## CLI Reference
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
looper-dashboard [OPTIONS]
|
|
118
|
+
|
|
119
|
+
Options:
|
|
120
|
+
--suite SUITE Test suite type: karate, playwright, playwright-monocart [required]
|
|
121
|
+
--config PATH Path to daily_flows.yml [required]
|
|
122
|
+
--scm-url URL Override scm_url from daily_flows.yml
|
|
123
|
+
--output PATH Output HTML file path (default varies by suite)
|
|
124
|
+
--days N Days of build history to fetch (default: 14 or 30)
|
|
125
|
+
--open Auto-open dashboard in browser when done
|
|
126
|
+
--no-ai Skip the AI failure analysis step
|
|
127
|
+
--no-details Skip fetching test summary details (faster, status only)
|
|
128
|
+
--save-failures PATH Path to save failures JSON
|
|
129
|
+
--analysis-json PATH Use a pre-computed AI analysis JSON instead of running live AI
|
|
130
|
+
--init SUITE Scaffold a starter daily_flows.yml and exit
|
|
131
|
+
|
|
132
|
+
-h, --help Show help and exit
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## AI Analysis
|
|
138
|
+
|
|
139
|
+
When `--no-ai` is not set, the orchestrator:
|
|
140
|
+
|
|
141
|
+
1. Saves a `*-failures.json` summary of all failing flows
|
|
142
|
+
2. Invokes `code-puppy` to classify each failure as one of:
|
|
143
|
+
- `service_regression` — likely a code regression in the app
|
|
144
|
+
- `environment_issue` — infra/network/auth failure
|
|
145
|
+
- `test_issue` — stale selector or bad test data
|
|
146
|
+
- `flaky` — intermittent, no clear pattern
|
|
147
|
+
3. Regenerates the dashboard HTML with an AI analysis panel embedded
|
|
148
|
+
|
|
149
|
+
If `code-puppy` is not installed, the AI step is skipped gracefully.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Publishing
|
|
154
|
+
|
|
155
|
+
### To public PyPI
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
cd looper-dashboard
|
|
159
|
+
pip install hatch
|
|
160
|
+
hatch build # creates dist/looper_dashboard-0.1.0.tar.gz and .whl
|
|
161
|
+
hatch publish # prompts for PyPI API token
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### To Walmart internal PyPI registry
|
|
165
|
+
|
|
166
|
+
Add the registry to `~/.config/hatch/config.toml`:
|
|
167
|
+
|
|
168
|
+
```toml
|
|
169
|
+
[publish.index.repos.internal]
|
|
170
|
+
url = "https://your-internal-pypi-registry/simple/"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Then:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
hatch publish --repo internal
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Onboarding a new repo
|
|
182
|
+
|
|
183
|
+
1. Create a `daily_flows.yml` for the target repo (use `--init` to scaffold)
|
|
184
|
+
2. Add job and flow names (find them in LooperPro or via `mcp-cli`)
|
|
185
|
+
3. Run `looper-dashboard --suite <type> --config daily_flows.yml --open`
|
|
186
|
+
|
|
187
|
+
No code changes required — the package is entirely configuration-driven.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""AI failure analyzer for Playwright and Monocart dashboards.
|
|
2
|
+
|
|
3
|
+
Invokes playwright-monocart-helper via the code-puppy CLI to batch-analyze
|
|
4
|
+
all failing/flaky flows and return structured classifications.
|
|
5
|
+
|
|
6
|
+
Usage (standalone):
|
|
7
|
+
python3 -m looper_dashboard.ai_analyzer
|
|
8
|
+
python3 -m looper_dashboard.ai_analyzer --failures failures.json --output analysis.json
|
|
9
|
+
"""
|
|
10
|
+
import base64
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# Constants
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
_VENV_BIN = Path.home() / ".code-puppy-venv" / "bin" / "code-puppy"
|
|
22
|
+
AGENT = "playwright-monocart-helper"
|
|
23
|
+
TIMEOUT = 240 # seconds
|
|
24
|
+
MAX_TITLES = 15 # max test titles per flow to keep token usage reasonable
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# ANSI / OSC stripping
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
def _strip_ansi(text: str) -> str:
|
|
32
|
+
"""Remove ANSI/VT escape sequences (including OSC) from text."""
|
|
33
|
+
text = re.sub(r'\x1b\[[0-9;]*[A-Za-z]', '', text)
|
|
34
|
+
text = re.sub(r'\x1b\].*?(?:\x07|\x1b\\)', '', text, flags=re.DOTALL)
|
|
35
|
+
text = re.sub(r'\x1b.', '', text)
|
|
36
|
+
return text
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _extract_json_from_osc52(text: str) -> list | None:
|
|
40
|
+
"""Extract and decode a base64 payload from an OSC 52 clipboard sequence."""
|
|
41
|
+
m = re.search(r']\s*52\s*;\s*c\s*;\s*([A-Za-z0-9+/=]+)', text)
|
|
42
|
+
if not m:
|
|
43
|
+
return None
|
|
44
|
+
try:
|
|
45
|
+
decoded = base64.b64decode(m.group(1)).decode("utf-8")
|
|
46
|
+
return json.loads(decoded)
|
|
47
|
+
except Exception: # noqa: BLE001
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Prompt construction
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
def _build_prompt(failures: list[dict]) -> str:
|
|
56
|
+
trimmed = [
|
|
57
|
+
{
|
|
58
|
+
**f,
|
|
59
|
+
"failedTests": (f.get("failedTests") or [])[:MAX_TITLES],
|
|
60
|
+
"flakyTests": (f.get("flakyTests") or [])[:MAX_TITLES],
|
|
61
|
+
}
|
|
62
|
+
for f in failures
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
return f"""You are analyzing Playwright test failures from a CI/CD pipeline.
|
|
66
|
+
|
|
67
|
+
For each failing flow below, classify the root cause and provide actionable guidance.
|
|
68
|
+
|
|
69
|
+
Failures:
|
|
70
|
+
{json.dumps(trimmed, indent=2)}
|
|
71
|
+
|
|
72
|
+
Respond with ONLY a raw JSON array (no markdown fences, no explanation).
|
|
73
|
+
Each element must have exactly these fields:
|
|
74
|
+
"flow" - (string) the flowName from the input
|
|
75
|
+
"job" - (string) the job short name from the input
|
|
76
|
+
"classification" - one of: "service_regression", "environment_issue", "test_issue", "flaky"
|
|
77
|
+
"severity" - one of: "high", "medium", "low"
|
|
78
|
+
"summary" - (string <=120 chars) what is failing and likely why, inferred from test names
|
|
79
|
+
"recommendation" - (string <=200 chars) concrete next step to fix or investigate
|
|
80
|
+
|
|
81
|
+
Classification guide:
|
|
82
|
+
service_regression - feature tests failing; likely a code regression in the app
|
|
83
|
+
environment_issue - infra/network/auth failures or env-specific issues
|
|
84
|
+
test_issue - stale selectors, bad test data, or a test code bug
|
|
85
|
+
flaky - intermittent with no clear pattern
|
|
86
|
+
|
|
87
|
+
Start your response with [ and end with ].
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# JSON extraction
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
def _extract_json(text: str) -> list | None:
|
|
96
|
+
"""Robustly extract a JSON array from code-puppy output."""
|
|
97
|
+
osc = _extract_json_from_osc52(text)
|
|
98
|
+
if osc is not None:
|
|
99
|
+
return osc
|
|
100
|
+
|
|
101
|
+
stripped = _strip_ansi(text).strip()
|
|
102
|
+
|
|
103
|
+
if stripped.startswith("["):
|
|
104
|
+
try:
|
|
105
|
+
return json.loads(stripped)
|
|
106
|
+
except json.JSONDecodeError:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
m = re.search(r"```(?:json)?\s*(\[.*?\])\s*```", stripped, re.DOTALL)
|
|
110
|
+
if m:
|
|
111
|
+
try:
|
|
112
|
+
return json.loads(m.group(1))
|
|
113
|
+
except json.JSONDecodeError:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
m = re.search(r"(\[.*\])", stripped, re.DOTALL)
|
|
117
|
+
if m:
|
|
118
|
+
try:
|
|
119
|
+
return json.loads(m.group(1))
|
|
120
|
+
except json.JSONDecodeError:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Public API
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
def analyze_failures(
|
|
131
|
+
failures: list[dict],
|
|
132
|
+
code_puppy_bin: str | None = None,
|
|
133
|
+
) -> list[dict]:
|
|
134
|
+
"""Invoke playwright-monocart-helper to classify each failing/flaky flow.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
failures: List of failure dicts from save_failure_summary.
|
|
138
|
+
code_puppy_bin: Override path to code-puppy binary.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of analysis dicts, or [] on any failure (never raises).
|
|
142
|
+
"""
|
|
143
|
+
if not failures:
|
|
144
|
+
return []
|
|
145
|
+
|
|
146
|
+
bin_path = Path(code_puppy_bin or _VENV_BIN)
|
|
147
|
+
if not bin_path.exists():
|
|
148
|
+
print(f"Warning: code-puppy not found at {bin_path}", file=sys.stderr)
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
prompt = _build_prompt(failures)
|
|
152
|
+
n = len(failures)
|
|
153
|
+
print(f"Invoking {AGENT} to analyze {n} failing flow{'s' if n != 1 else ''}...", file=sys.stderr)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
result = subprocess.run(
|
|
157
|
+
[str(bin_path), "--prompt", prompt, "--agent", AGENT],
|
|
158
|
+
capture_output=True,
|
|
159
|
+
text=True,
|
|
160
|
+
timeout=TIMEOUT,
|
|
161
|
+
)
|
|
162
|
+
except subprocess.TimeoutExpired:
|
|
163
|
+
print(f"Warning: AI analysis timed out after {TIMEOUT}s.", file=sys.stderr)
|
|
164
|
+
return []
|
|
165
|
+
except Exception as exc: # noqa: BLE001
|
|
166
|
+
print(f"Warning: AI analysis subprocess error: {exc}", file=sys.stderr)
|
|
167
|
+
return []
|
|
168
|
+
|
|
169
|
+
raw = result.stdout or ""
|
|
170
|
+
analysis = _extract_json(raw)
|
|
171
|
+
|
|
172
|
+
if analysis is None and result.stderr:
|
|
173
|
+
analysis = _extract_json(result.stderr)
|
|
174
|
+
|
|
175
|
+
if analysis is None:
|
|
176
|
+
print("Warning: Could not parse JSON from AI output.", file=sys.stderr)
|
|
177
|
+
tail = (raw or result.stderr or "").strip().splitlines()
|
|
178
|
+
for line in tail[-8:]:
|
|
179
|
+
print(f" | {line}", file=sys.stderr)
|
|
180
|
+
return []
|
|
181
|
+
|
|
182
|
+
print(f"AI analysis complete — {len(analysis)} flow{'s' if len(analysis) != 1 else ''} classified.", file=sys.stderr)
|
|
183
|
+
return analysis
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
# CLI (standalone use)
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
def main() -> None:
|
|
191
|
+
import argparse
|
|
192
|
+
|
|
193
|
+
parser = argparse.ArgumentParser(
|
|
194
|
+
description="Analyze Playwright/Monocart failures with playwright-monocart-helper AI",
|
|
195
|
+
)
|
|
196
|
+
parser.add_argument("--failures", default="failures.json", help="Path to failures JSON")
|
|
197
|
+
parser.add_argument("--output", default="ai-analysis.json", help="Output path for analysis JSON")
|
|
198
|
+
parser.add_argument("--code-puppy-bin", default=None, help=f"Override path to code-puppy (default: {_VENV_BIN})")
|
|
199
|
+
args = parser.parse_args()
|
|
200
|
+
|
|
201
|
+
failures_path = Path(args.failures)
|
|
202
|
+
if not failures_path.exists():
|
|
203
|
+
print(f"Failures file not found: {failures_path}", file=sys.stderr)
|
|
204
|
+
sys.exit(1)
|
|
205
|
+
|
|
206
|
+
failures = json.loads(failures_path.read_text())
|
|
207
|
+
if not failures:
|
|
208
|
+
print("No failures to analyze.", file=sys.stderr)
|
|
209
|
+
sys.exit(0)
|
|
210
|
+
|
|
211
|
+
analysis = analyze_failures(failures, code_puppy_bin=args.code_puppy_bin)
|
|
212
|
+
if not analysis:
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
|
|
215
|
+
output_path = Path(args.output)
|
|
216
|
+
output_path.write_text(json.dumps(analysis, indent=2))
|
|
217
|
+
print(f"Analysis saved to {output_path.resolve()}", file=sys.stderr)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
main()
|