feat(dev): add dev.abaci.one for build artifacts
- Add nginx static server at dev.abaci.one for serving: - Playwright HTML reports at /smoke-reports/ - Storybook (future) at /storybook/ - Coverage reports (future) at /coverage/ - NFS-backed PVC shared between artifact producers and nginx - Smoke tests now save HTML reports with automatic cleanup (keeps 20) - Reports accessible at dev.abaci.one/smoke-reports/latest/ Infrastructure: - infra/terraform/dev-artifacts.tf: nginx deployment, PVC, ingress - Updated smoke-tests.tf to mount shared PVC - Updated smoke-test-runner.ts to generate and save HTML reports Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
87bce550ad
commit
5258437bef
|
|
@ -3,23 +3,35 @@
|
||||||
* Smoke Test Runner
|
* Smoke Test Runner
|
||||||
*
|
*
|
||||||
* Runs Playwright smoke tests and reports results to the abaci-app API.
|
* Runs Playwright smoke tests and reports results to the abaci-app API.
|
||||||
|
* Optionally saves HTML reports to a filesystem directory for viewing.
|
||||||
*
|
*
|
||||||
* Environment variables:
|
* Environment variables:
|
||||||
* - BASE_URL: The base URL to test against (default: http://localhost:3000)
|
* - BASE_URL: The base URL to test against (default: http://localhost:3000)
|
||||||
* - RESULTS_API_URL: The URL to POST results to (default: http://localhost:3000/api/smoke-test-results)
|
* - RESULTS_API_URL: The URL to POST results to (default: http://localhost:3000/api/smoke-test-results)
|
||||||
|
* - REPORT_DIR: Directory to save HTML reports (optional, e.g., /artifacts/smoke-reports)
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* npx tsx scripts/smoke-test-runner.ts
|
* npx tsx scripts/smoke-test-runner.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { spawn } from "child_process";
|
import { spawn, execSync } from "child_process";
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import { existsSync, readFileSync, mkdirSync } from "fs";
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
cpSync,
|
||||||
|
rmSync,
|
||||||
|
symlinkSync,
|
||||||
|
unlinkSync,
|
||||||
|
readdirSync,
|
||||||
|
statSync,
|
||||||
|
} from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
const BASE_URL = process.env.BASE_URL || "http://localhost:3000";
|
const BASE_URL = process.env.BASE_URL || "http://localhost:3000";
|
||||||
const RESULTS_API_URL =
|
const RESULTS_API_URL =
|
||||||
process.env.RESULTS_API_URL || `${BASE_URL}/api/smoke-test-results`;
|
process.env.RESULTS_API_URL || `${BASE_URL}/api/smoke-test-results`;
|
||||||
|
const REPORT_DIR = process.env.REPORT_DIR; // Optional: directory to save HTML reports
|
||||||
|
|
||||||
interface PlaywrightTestResult {
|
interface PlaywrightTestResult {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -84,6 +96,96 @@ async function reportResults(results: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save HTML report to the artifacts directory
|
||||||
|
*/
|
||||||
|
function saveHtmlReport(
|
||||||
|
runId: string,
|
||||||
|
htmlReportDir: string,
|
||||||
|
passed: boolean,
|
||||||
|
): string | null {
|
||||||
|
if (!REPORT_DIR) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure report directory exists
|
||||||
|
if (!existsSync(REPORT_DIR)) {
|
||||||
|
mkdirSync(REPORT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create run-specific directory with timestamp prefix for sorting
|
||||||
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||||
|
const status = passed ? "passed" : "failed";
|
||||||
|
const reportDirName = `${timestamp}_${status}_${runId.slice(0, 8)}`;
|
||||||
|
const destDir = join(REPORT_DIR, reportDirName);
|
||||||
|
|
||||||
|
// Copy HTML report to destination
|
||||||
|
if (existsSync(htmlReportDir)) {
|
||||||
|
cpSync(htmlReportDir, destDir, { recursive: true });
|
||||||
|
console.log(`HTML report saved to: ${destDir}`);
|
||||||
|
|
||||||
|
// Update "latest" symlink
|
||||||
|
const latestLink = join(REPORT_DIR, "latest");
|
||||||
|
try {
|
||||||
|
if (existsSync(latestLink)) {
|
||||||
|
unlinkSync(latestLink);
|
||||||
|
}
|
||||||
|
symlinkSync(reportDirName, latestLink);
|
||||||
|
console.log(`Updated 'latest' symlink to: ${reportDirName}`);
|
||||||
|
} catch (symlinkError) {
|
||||||
|
console.warn("Could not create latest symlink:", symlinkError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up old reports (keep last 20)
|
||||||
|
cleanupOldReports(20);
|
||||||
|
|
||||||
|
return reportDirName;
|
||||||
|
} else {
|
||||||
|
console.warn(`HTML report directory not found: ${htmlReportDir}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving HTML report:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove old reports, keeping only the most recent N
|
||||||
|
*/
|
||||||
|
function cleanupOldReports(keepCount: number): void {
|
||||||
|
if (!REPORT_DIR || !existsSync(REPORT_DIR)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entries = readdirSync(REPORT_DIR)
|
||||||
|
.filter((name) => {
|
||||||
|
// Only consider directories that match our naming pattern (timestamp_status_id)
|
||||||
|
const fullPath = join(REPORT_DIR, name);
|
||||||
|
return (
|
||||||
|
name !== "latest" &&
|
||||||
|
existsSync(fullPath) &&
|
||||||
|
statSync(fullPath).isDirectory() &&
|
||||||
|
/^\d{4}-\d{2}-\d{2}T/.test(name)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.sort()
|
||||||
|
.reverse(); // Most recent first
|
||||||
|
|
||||||
|
// Remove old entries
|
||||||
|
const toRemove = entries.slice(keepCount);
|
||||||
|
for (const dir of toRemove) {
|
||||||
|
const fullPath = join(REPORT_DIR, dir);
|
||||||
|
rmSync(fullPath, { recursive: true, force: true });
|
||||||
|
console.log(`Removed old report: ${dir}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cleaning up old reports:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function runTests(): Promise<void> {
|
async function runTests(): Promise<void> {
|
||||||
const runId = randomUUID();
|
const runId = randomUUID();
|
||||||
const startedAt = new Date().toISOString();
|
const startedAt = new Date().toISOString();
|
||||||
|
|
@ -100,15 +202,19 @@ async function runTests(): Promise<void> {
|
||||||
});
|
});
|
||||||
|
|
||||||
const reportDir = join(process.cwd(), "playwright-report");
|
const reportDir = join(process.cwd(), "playwright-report");
|
||||||
|
const htmlReportDir = join(process.cwd(), "playwright-html-report");
|
||||||
const jsonReportPath = join(reportDir, "results.json");
|
const jsonReportPath = join(reportDir, "results.json");
|
||||||
|
|
||||||
// Ensure report directory exists
|
// Ensure report directories exist
|
||||||
if (!existsSync(reportDir)) {
|
if (!existsSync(reportDir)) {
|
||||||
mkdirSync(reportDir, { recursive: true });
|
mkdirSync(reportDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
if (!existsSync(htmlReportDir)) {
|
||||||
|
mkdirSync(htmlReportDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Run Playwright tests with JSON reporter
|
// Run Playwright tests with JSON reporter (stdout) and HTML reporter (directory)
|
||||||
// Note: testDir in playwright.config.ts is './e2e', so we pass 'smoke' not 'e2e/smoke'
|
// Note: testDir in playwright.config.ts is './e2e', so we pass 'smoke' not 'e2e/smoke'
|
||||||
const playwrightProcess = spawn(
|
const playwrightProcess = spawn(
|
||||||
"npx",
|
"npx",
|
||||||
|
|
@ -116,7 +222,7 @@ async function runTests(): Promise<void> {
|
||||||
"playwright",
|
"playwright",
|
||||||
"test",
|
"test",
|
||||||
"smoke",
|
"smoke",
|
||||||
"--reporter=json",
|
"--reporter=json,html",
|
||||||
`--output=${reportDir}`,
|
`--output=${reportDir}`,
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
|
|
@ -125,6 +231,7 @@ async function runTests(): Promise<void> {
|
||||||
...process.env,
|
...process.env,
|
||||||
BASE_URL,
|
BASE_URL,
|
||||||
CI: "true", // Ensure CI mode
|
CI: "true", // Ensure CI mode
|
||||||
|
PLAYWRIGHT_HTML_REPORT: htmlReportDir, // Output directory for HTML report
|
||||||
},
|
},
|
||||||
stdio: ["inherit", "pipe", "pipe"],
|
stdio: ["inherit", "pipe", "pipe"],
|
||||||
},
|
},
|
||||||
|
|
@ -165,12 +272,19 @@ async function runTests(): Promise<void> {
|
||||||
report.stats.expected + report.stats.unexpected + report.stats.skipped;
|
report.stats.expected + report.stats.unexpected + report.stats.skipped;
|
||||||
const passedTests = report.stats.expected;
|
const passedTests = report.stats.expected;
|
||||||
const failedTests = report.stats.unexpected;
|
const failedTests = report.stats.unexpected;
|
||||||
|
const passed = failedTests === 0;
|
||||||
|
|
||||||
|
// Save HTML report to artifacts directory
|
||||||
|
const savedReportDir = saveHtmlReport(runId, htmlReportDir, passed);
|
||||||
|
if (savedReportDir) {
|
||||||
|
console.log(`HTML report: https://dev.abaci.one/smoke-reports/${savedReportDir}/`);
|
||||||
|
}
|
||||||
|
|
||||||
await reportResults({
|
await reportResults({
|
||||||
id: runId,
|
id: runId,
|
||||||
startedAt,
|
startedAt,
|
||||||
completedAt,
|
completedAt,
|
||||||
status: failedTests > 0 ? "failed" : "passed",
|
status: passed ? "passed" : "failed",
|
||||||
totalTests,
|
totalTests,
|
||||||
passedTests,
|
passedTests,
|
||||||
failedTests,
|
failedTests,
|
||||||
|
|
@ -187,11 +301,19 @@ async function runTests(): Promise<void> {
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: report based on exit code
|
// Fallback: report based on exit code
|
||||||
|
const passed = exitCode === 0;
|
||||||
|
|
||||||
|
// Save HTML report to artifacts directory
|
||||||
|
const savedReportDir = saveHtmlReport(runId, htmlReportDir, passed);
|
||||||
|
if (savedReportDir) {
|
||||||
|
console.log(`HTML report: https://dev.abaci.one/smoke-reports/${savedReportDir}/`);
|
||||||
|
}
|
||||||
|
|
||||||
await reportResults({
|
await reportResults({
|
||||||
id: runId,
|
id: runId,
|
||||||
startedAt,
|
startedAt,
|
||||||
completedAt,
|
completedAt,
|
||||||
status: exitCode === 0 ? "passed" : "failed",
|
status: passed ? "passed" : "failed",
|
||||||
durationMs,
|
durationMs,
|
||||||
errorMessage:
|
errorMessage:
|
||||||
exitCode !== 0
|
exitCode !== 0
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
# Dev Artifacts Server
|
||||||
|
#
|
||||||
|
# Serves static build artifacts at dev.abaci.one:
|
||||||
|
# - /smoke-reports/ - Playwright HTML reports from smoke tests
|
||||||
|
# - /storybook/ - Component library documentation
|
||||||
|
# - /coverage/ - Test coverage reports (future)
|
||||||
|
#
|
||||||
|
# Architecture:
|
||||||
|
# - NFS-backed PVC shared between artifact producers and nginx
|
||||||
|
# - nginx serves files read-only with directory listing enabled
|
||||||
|
# - Smoke tests CronJob writes reports directly to filesystem
|
||||||
|
|
||||||
|
# NFS PersistentVolume for dev artifacts
|
||||||
|
resource "kubernetes_persistent_volume" "dev_artifacts" {
|
||||||
|
metadata {
|
||||||
|
name = "dev-artifacts-pv"
|
||||||
|
labels = {
|
||||||
|
type = "nfs"
|
||||||
|
app = "dev-artifacts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
capacity = {
|
||||||
|
storage = "10Gi"
|
||||||
|
}
|
||||||
|
access_modes = ["ReadWriteMany"]
|
||||||
|
persistent_volume_reclaim_policy = "Retain"
|
||||||
|
storage_class_name = "nfs"
|
||||||
|
|
||||||
|
persistent_volume_source {
|
||||||
|
nfs {
|
||||||
|
server = var.nfs_server
|
||||||
|
path = "/volume1/homes/antialias/projects/abaci.one/dev-artifacts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_persistent_volume_claim" "dev_artifacts" {
|
||||||
|
metadata {
|
||||||
|
name = "dev-artifacts"
|
||||||
|
namespace = kubernetes_namespace.abaci.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
access_modes = ["ReadWriteMany"]
|
||||||
|
storage_class_name = "nfs"
|
||||||
|
|
||||||
|
resources {
|
||||||
|
requests = {
|
||||||
|
storage = "10Gi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selector {
|
||||||
|
match_labels = {
|
||||||
|
type = "nfs"
|
||||||
|
app = "dev-artifacts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# nginx ConfigMap for custom configuration
|
||||||
|
resource "kubernetes_config_map" "dev_artifacts_nginx" {
|
||||||
|
metadata {
|
||||||
|
name = "dev-artifacts-nginx"
|
||||||
|
namespace = kubernetes_namespace.abaci.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"default.conf" = <<-EOT
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
# Enable directory listing
|
||||||
|
autoindex on;
|
||||||
|
autoindex_exact_size off;
|
||||||
|
autoindex_localtime on;
|
||||||
|
|
||||||
|
# Serve static files with caching
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTML files - no cache for fresh reports
|
||||||
|
location ~* \.html$ {
|
||||||
|
expires -1;
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check endpoint
|
||||||
|
location /health {
|
||||||
|
return 200 'ok';
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# nginx Deployment
|
||||||
|
resource "kubernetes_deployment" "dev_artifacts" {
|
||||||
|
metadata {
|
||||||
|
name = "dev-artifacts"
|
||||||
|
namespace = kubernetes_namespace.abaci.metadata[0].name
|
||||||
|
labels = {
|
||||||
|
app = "dev-artifacts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
replicas = 1
|
||||||
|
|
||||||
|
selector {
|
||||||
|
match_labels = {
|
||||||
|
app = "dev-artifacts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
metadata {
|
||||||
|
labels = {
|
||||||
|
app = "dev-artifacts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
container {
|
||||||
|
name = "nginx"
|
||||||
|
image = "nginx:1.25-alpine"
|
||||||
|
|
||||||
|
port {
|
||||||
|
container_port = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
name = "artifacts"
|
||||||
|
mount_path = "/usr/share/nginx/html"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
name = "nginx-config"
|
||||||
|
mount_path = "/etc/nginx/conf.d"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
requests = {
|
||||||
|
memory = "32Mi"
|
||||||
|
cpu = "10m"
|
||||||
|
}
|
||||||
|
limits = {
|
||||||
|
memory = "64Mi"
|
||||||
|
cpu = "100m"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
liveness_probe {
|
||||||
|
http_get {
|
||||||
|
path = "/health"
|
||||||
|
port = 80
|
||||||
|
}
|
||||||
|
initial_delay_seconds = 5
|
||||||
|
period_seconds = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
readiness_probe {
|
||||||
|
http_get {
|
||||||
|
path = "/health"
|
||||||
|
port = 80
|
||||||
|
}
|
||||||
|
initial_delay_seconds = 2
|
||||||
|
period_seconds = 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volume {
|
||||||
|
name = "artifacts"
|
||||||
|
persistent_volume_claim {
|
||||||
|
claim_name = kubernetes_persistent_volume_claim.dev_artifacts.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volume {
|
||||||
|
name = "nginx-config"
|
||||||
|
config_map {
|
||||||
|
name = kubernetes_config_map.dev_artifacts_nginx.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Service for nginx
|
||||||
|
resource "kubernetes_service" "dev_artifacts" {
|
||||||
|
metadata {
|
||||||
|
name = "dev-artifacts"
|
||||||
|
namespace = kubernetes_namespace.abaci.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
selector = {
|
||||||
|
app = "dev-artifacts"
|
||||||
|
}
|
||||||
|
|
||||||
|
port {
|
||||||
|
port = 80
|
||||||
|
target_port = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
type = "ClusterIP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ingress for dev.abaci.one
|
||||||
|
resource "kubernetes_ingress_v1" "dev_artifacts" {
|
||||||
|
metadata {
|
||||||
|
name = "dev-artifacts"
|
||||||
|
namespace = kubernetes_namespace.abaci.metadata[0].name
|
||||||
|
annotations = {
|
||||||
|
"cert-manager.io/cluster-issuer" = var.use_staging_certs ? "letsencrypt-staging" : "letsencrypt-prod"
|
||||||
|
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
|
||||||
|
"traefik.ingress.kubernetes.io/router.middlewares" = "${kubernetes_namespace.abaci.metadata[0].name}-hsts@kubernetescrd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
ingress_class_name = "traefik"
|
||||||
|
|
||||||
|
tls {
|
||||||
|
hosts = ["dev.${var.app_domain}"]
|
||||||
|
secret_name = "dev-artifacts-tls"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule {
|
||||||
|
host = "dev.${var.app_domain}"
|
||||||
|
|
||||||
|
http {
|
||||||
|
path {
|
||||||
|
path = "/"
|
||||||
|
path_type = "Prefix"
|
||||||
|
|
||||||
|
backend {
|
||||||
|
service {
|
||||||
|
name = kubernetes_service.dev_artifacts.metadata[0].name
|
||||||
|
port {
|
||||||
|
number = 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [null_resource.cert_manager_issuers]
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP to HTTPS redirect for dev subdomain
|
||||||
|
resource "kubernetes_ingress_v1" "dev_artifacts_http_redirect" {
|
||||||
|
metadata {
|
||||||
|
name = "dev-artifacts-http-redirect"
|
||||||
|
namespace = kubernetes_namespace.abaci.metadata[0].name
|
||||||
|
annotations = {
|
||||||
|
"traefik.ingress.kubernetes.io/router.entrypoints" = "web"
|
||||||
|
"traefik.ingress.kubernetes.io/router.middlewares" = "${kubernetes_namespace.abaci.metadata[0].name}-redirect-https@kubernetescrd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
ingress_class_name = "traefik"
|
||||||
|
|
||||||
|
rule {
|
||||||
|
host = "dev.${var.app_domain}"
|
||||||
|
|
||||||
|
http {
|
||||||
|
path {
|
||||||
|
path = "/"
|
||||||
|
path_type = "Prefix"
|
||||||
|
|
||||||
|
backend {
|
||||||
|
service {
|
||||||
|
name = kubernetes_service.dev_artifacts.metadata[0].name
|
||||||
|
port {
|
||||||
|
number = 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# Runs Playwright smoke tests every 15 minutes and reports results to the app API.
|
# Runs Playwright smoke tests every 15 minutes and reports results to the app API.
|
||||||
# Results are exposed via /api/smoke-test-status for Gatus monitoring.
|
# Results are exposed via /api/smoke-test-status for Gatus monitoring.
|
||||||
|
# HTML reports are saved to the dev-artifacts PVC for viewing at dev.abaci.one/smoke-reports/
|
||||||
|
|
||||||
resource "kubernetes_cron_job_v1" "smoke_tests" {
|
resource "kubernetes_cron_job_v1" "smoke_tests" {
|
||||||
metadata {
|
metadata {
|
||||||
|
|
@ -65,6 +66,12 @@ resource "kubernetes_cron_job_v1" "smoke_tests" {
|
||||||
value = "http://abaci-app-primary.${kubernetes_namespace.abaci.metadata[0].name}.svc.cluster.local/api/smoke-test-results"
|
value = "http://abaci-app-primary.${kubernetes_namespace.abaci.metadata[0].name}.svc.cluster.local/api/smoke-test-results"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
# Directory to save HTML reports (on shared PVC)
|
||||||
|
name = "REPORT_DIR"
|
||||||
|
value = "/artifacts/smoke-reports"
|
||||||
|
}
|
||||||
|
|
||||||
resources {
|
resources {
|
||||||
requests = {
|
requests = {
|
||||||
memory = "512Mi"
|
memory = "512Mi"
|
||||||
|
|
@ -81,6 +88,12 @@ resource "kubernetes_cron_job_v1" "smoke_tests" {
|
||||||
name = "dshm"
|
name = "dshm"
|
||||||
mount_path = "/dev/shm"
|
mount_path = "/dev/shm"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Mount dev-artifacts PVC for saving HTML reports
|
||||||
|
volume_mount {
|
||||||
|
name = "artifacts"
|
||||||
|
mount_path = "/artifacts"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Chromium needs shared memory for rendering
|
# Chromium needs shared memory for rendering
|
||||||
|
|
@ -92,6 +105,14 @@ resource "kubernetes_cron_job_v1" "smoke_tests" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Shared storage for artifacts (HTML reports)
|
||||||
|
volume {
|
||||||
|
name = "artifacts"
|
||||||
|
persistent_volume_claim {
|
||||||
|
claim_name = kubernetes_persistent_volume_claim.dev_artifacts.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
restart_policy = "Never"
|
restart_policy = "Never"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue