Skip to content

Commit 4ff982c

Browse files
committed
add automated UI schema QA verification checks
1 parent adbdd54 commit 4ff982c

2 files changed

Lines changed: 210 additions & 0 deletions

File tree

.github/workflows/periodic-qa.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
working-directory: ./agent_sdks/python
46+
run: uv run python ../../scripts/qa_report.py

scripts/qa_report.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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+
37+
def run_validation():
38+
print(f"Starting QA Validation against A2UI v{VERSION_0_9}...")
39+
40+
reports_dir = ROOT_DIR / "qa_reports"
41+
reports_dir.mkdir(exist_ok=True)
42+
43+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
44+
report_path = reports_dir / f"report_{timestamp}.md"
45+
46+
report_content = [
47+
f"# A2UI Periodic QA Report",
48+
f"**Date:** {datetime.now().isoformat()}",
49+
f"**Specification Version:** {VERSION_0_9}",
50+
"",
51+
"## Validated Samples",
52+
""
53+
]
54+
55+
samples_base = ROOT_DIR / "samples" / "agent" / "adk"
56+
57+
samples_to_test = [
58+
{
59+
"name": "custom-components-example (Contact Manager)",
60+
"path": samples_base / "custom-components-example",
61+
"catalogs_func": lambda: [
62+
CatalogConfig.from_path(
63+
name="inline",
64+
catalog_path="inline_catalog_0.9.json",
65+
examples_path="examples/0.9"
66+
),
67+
BasicCatalog.get_config(version=VERSION_0_9)
68+
],
69+
"examples_path": "examples/0.9"
70+
},
71+
{
72+
"name": "restaurant_finder",
73+
"path": samples_base / "restaurant_finder",
74+
"catalogs_func": lambda: [BasicCatalog.get_config(version=VERSION_0_9)],
75+
"examples_path": "examples/0.9"
76+
}
77+
]
78+
79+
total_examples = 0
80+
passed_examples = 0
81+
failed_examples = []
82+
83+
for sample in samples_to_test:
84+
sample_name = sample["name"]
85+
sample_dir = sample["path"]
86+
87+
print(f"Validating sample: {sample_name}")
88+
report_content.append(f"### {sample_name}")
89+
90+
if not sample_dir.exists():
91+
err = f"Error: Sample directory {sample_dir} does not exist."
92+
print(err)
93+
report_content.append(f"- ❌ {err}")
94+
continue
95+
96+
os.chdir(sample_dir)
97+
try:
98+
catalogs = sample["catalogs_func"]()
99+
manager = A2uiSchemaManager(
100+
VERSION_0_9,
101+
catalogs=catalogs,
102+
accepts_inline_catalogs=True,
103+
schema_modifiers=[remove_strict_validation]
104+
)
105+
106+
examples_dir = sample_dir / sample["examples_path"]
107+
if not examples_dir.exists():
108+
err = f"Examples dir {examples_dir} not found."
109+
print(err)
110+
report_content.append(f"- ❌ {err}")
111+
continue
112+
113+
# We validate against the FIRST catalog (or inline merged)
114+
catalog = manager.get_selected_catalog()
115+
116+
for filename in sorted(os.listdir(examples_dir)):
117+
filepath = examples_dir / filename
118+
if not filepath.is_file() or not filename.endswith(".json"):
119+
continue
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_msg = getattr(e, 'message', str(e))
131+
err_str = str(err_msg).replace('\n', ' ')
132+
report_content.append(f"- ❌ `{filename}`: Failed ({err_str})")
133+
failed_examples.append(f"{sample_name}/{filename}")
134+
135+
except Exception as e:
136+
err = f"Error setting up schema manager: {e}"
137+
print(err)
138+
traceback.print_exc()
139+
report_content.append(f"- ❌ {err}")
140+
141+
report_content.append("")
142+
143+
# Summary section
144+
report_content.append("## Summary")
145+
report_content.append(f"- **Total Examples Validated:** {total_examples}")
146+
report_content.append(f"- **Passed:** {passed_examples}")
147+
report_content.append(f"- **Failed:** {len(failed_examples)}")
148+
149+
report_str = "\n".join(report_content)
150+
with open(report_path, "w", encoding="utf-8") as f:
151+
f.write(report_str)
152+
153+
print(f"\nQA Report generated at: {report_path}")
154+
155+
if failed_examples:
156+
print(f"Validation finished with {len(failed_examples)} failures.")
157+
sys.exit(1)
158+
else:
159+
print("Validation finished successfully.")
160+
sys.exit(0)
161+
162+
163+
if __name__ == "__main__":
164+
run_validation()

0 commit comments

Comments
 (0)