Skip to content

Commit a4a95dc

Browse files
committed
add automated periodic QA reporting and GitHub action
1 parent ec8e019 commit a4a95dc

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

.github/workflows/periodic-qa.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: A2UI Periodic QA-Demo Status Audit
16+
17+
on:
18+
push:
19+
branches: [ "main" ]
20+
paths:
21+
- 'specification/**/json/**'
22+
- 'scripts/qa_report.py'
23+
pull_request:
24+
paths:
25+
- 'specification/**/json/**'
26+
- 'scripts/qa_report.py'
27+
28+
jobs:
29+
static-ui-validation:
30+
runs-on: ubuntu-latest
31+
32+
steps:
33+
- name: Check out repository
34+
uses: actions/checkout@v4
35+
36+
- name: Install `uv` globally
37+
uses: astral-sh/setup-uv@v3
38+
39+
- name: Setup Python Architecture
40+
uses: actions/setup-python@v5
41+
with:
42+
python-version: '3.11'
43+
44+
- name: Execute Offline Validation Script
45+
run: uv run python scripts/qa_report.py

scripts/qa_report.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2026 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
# QA Report Script to validate A2UI samples against latest specification.
17+
18+
import os
19+
import sys
20+
import json
21+
from datetime import datetime
22+
from pathlib import Path
23+
import traceback
24+
25+
# Ensure we can import agent_sdks
26+
ROOT_DIR = Path(__file__).parent.parent.resolve()
27+
PYTHON_SDK_DIR = ROOT_DIR / "agent_sdks" / "python" / "src"
28+
if str(PYTHON_SDK_DIR) not in sys.path:
29+
sys.path.insert(0, str(PYTHON_SDK_DIR))
30+
31+
from a2ui.schema.manager import A2uiSchemaManager, CatalogConfig
32+
from a2ui.basic_catalog.provider import BasicCatalog
33+
from a2ui.schema.common_modifiers import remove_strict_validation
34+
from a2ui.schema.constants import VERSION_0_9
35+
36+
def run_validation():
37+
print(f"Starting QA Validation against A2UI v{VERSION_0_9}...")
38+
39+
reports_dir = ROOT_DIR / "qa_reports"
40+
reports_dir.mkdir(exist_ok=True)
41+
42+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
43+
report_path = reports_dir / f"report_{timestamp}.md"
44+
45+
report_content = [
46+
f"# A2UI Periodic QA Report",
47+
f"**Date:** {datetime.now().isoformat()}",
48+
f"**Specification Version:** {VERSION_0_9}",
49+
"",
50+
"## Validated Samples",
51+
""
52+
]
53+
54+
samples_base = ROOT_DIR / "samples" / "agent" / "adk"
55+
56+
samples_to_test = [
57+
{
58+
"name": "custom-components-example (Contact Manager)",
59+
"path": samples_base / "custom-components-example",
60+
"catalogs_func": lambda: [
61+
CatalogConfig.from_path(
62+
name="inline",
63+
catalog_path="inline_catalog_0.9.json",
64+
examples_path="examples/0.9"
65+
),
66+
BasicCatalog.get_config(version=VERSION_0_9)
67+
],
68+
"examples_path": "examples/0.9"
69+
},
70+
{
71+
"name": "restaurant_finder",
72+
"path": samples_base / "restaurant_finder",
73+
"catalogs_func": lambda: [BasicCatalog.get_config(version=VERSION_0_9)],
74+
"examples_path": "examples/0.9"
75+
}
76+
]
77+
78+
total_examples = 0
79+
passed_examples = 0
80+
failed_examples = []
81+
82+
for sample in samples_to_test:
83+
sample_name = sample["name"]
84+
sample_dir = sample["path"]
85+
86+
print(f"Validating sample: {sample_name}")
87+
report_content.append(f"### {sample_name}")
88+
89+
if not sample_dir.exists():
90+
err = f"Error: Sample directory {sample_dir} does not exist."
91+
print(err)
92+
report_content.append(f"- ❌ {err}")
93+
continue
94+
95+
os.chdir(sample_dir)
96+
try:
97+
catalogs = sample["catalogs_func"]()
98+
manager = A2uiSchemaManager(
99+
VERSION_0_9,
100+
catalogs=catalogs,
101+
accepts_inline_catalogs=True,
102+
schema_modifiers=[remove_strict_validation]
103+
)
104+
105+
examples_dir = sample_dir / sample["examples_path"]
106+
if not examples_dir.exists():
107+
err = f"Examples dir {examples_dir} not found."
108+
print(err)
109+
report_content.append(f"- ❌ {err}")
110+
continue
111+
112+
# We validate against the FIRST catalog (or inline merged)
113+
catalog = manager.get_selected_catalog()
114+
115+
for filename in sorted(os.listdir(examples_dir)):
116+
if not filename.endswith(".json"):
117+
continue
118+
119+
filepath = examples_dir / filename
120+
total_examples += 1
121+
122+
try:
123+
with open(filepath, "r", encoding="utf-8") as f:
124+
data = json.load(f)
125+
126+
catalog.validator.validate(data)
127+
report_content.append(f"- ✅ `{filename}`: Passed")
128+
passed_examples += 1
129+
except Exception as e:
130+
err_str = str(e).replace('\n', ' ')
131+
report_content.append(f"- ❌ `{filename}`: Failed ({err_str})")
132+
failed_examples.append(f"{sample_name}/{filename}")
133+
134+
except Exception as e:
135+
err = f"Error setting up schema manager: {e}"
136+
print(err)
137+
traceback.print_exc()
138+
report_content.append(f"- ❌ {err}")
139+
140+
report_content.append("")
141+
142+
# Summary section
143+
report_content.append("## Summary")
144+
report_content.append(f"- **Total Examples Validated:** {total_examples}")
145+
report_content.append(f"- **Passed:** {passed_examples}")
146+
report_content.append(f"- **Failed:** {len(failed_examples)}")
147+
148+
report_str = "\n".join(report_content)
149+
with open(report_path, "w") as f:
150+
f.write(report_str)
151+
152+
print(f"\nQA Report generated at: {report_path}")
153+
154+
if failed_examples:
155+
print(f"Validation finished with {len(failed_examples)} failures.")
156+
sys.exit(1)
157+
else:
158+
print("Validation finished successfully.")
159+
sys.exit(0)
160+
161+
if __name__ == "__main__":
162+
run_validation()

0 commit comments

Comments
 (0)