diff --git a/README.md b/README.md index d51be096..1c9832d0 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Native Playwright HTML reports are great for local debugging — but they're eph ```bash # Linux / macOS +mkdir -p .data && chown -R 1001:1001 .data # the container runs as non-root UID 1001 docker run -p 3000:3000 -v $(pwd)/.data:/app/.data phenx/piwitests-server:latest ``` @@ -76,6 +77,8 @@ docker run -p 3000:3000 -v ${PWD}/.data:/app/.data phenx/piwitests-server:latest Visit `http://localhost:3000`. A [`docker-compose.yml`](./docker-compose.yml) is also included. +> **Linux hosts:** the container runs as non-root UID 1001, so without the `chown` above, Docker auto-creates `.data` owned by `root` and the container can't write to it. Windows and macOS (Docker Desktop) don't need this step. See [Troubleshooting](./DOCKER.md#troubleshooting) if you hit a permission error. + > **Production tip:** set `PIWI_SECRET_KEY` (any long random string) so credentials you store in the dashboard — AI API keys, SCM tokens — are encrypted at rest. Generate one with `node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"`. See the [deployment guide](https://piwitests.github.io/deployment). **2. Add the reporter to your test project** diff --git a/application/app/components/cluster/ClusterTestEvidence.vue b/application/app/components/cluster/ClusterTestEvidence.vue index a1ccb45f..68b0a187 100644 --- a/application/app/components/cluster/ClusterTestEvidence.vue +++ b/application/app/components/cluster/ClusterTestEvidence.vue @@ -232,7 +232,10 @@ const evidenceChips = computed(() => { >
- +
diff --git a/application/app/components/layout/GetStartedWizard.vue b/application/app/components/layout/GetStartedWizard.vue index 2946585c..3549e3a9 100644 --- a/application/app/components/layout/GetStartedWizard.vue +++ b/application/app/components/layout/GetStartedWizard.vue @@ -1,12 +1,17 @@ @@ -114,7 +139,7 @@ const goFurtherOpen = ref(false);

Get started in 60 seconds

-

Send your first test run in four steps

+

Send your first test run in {{ stepCountWord }} steps

@@ -144,6 +169,15 @@ const goFurtherOpen = ref(false); Done

{{ step.description }}

+ + Create an API key + @@ -174,7 +208,7 @@ const goFurtherOpen = ref(false); auto-injects the reporter and chains the global setup in one call. It also registers the run on the dashboard before your globalSetup runs, so the - dashboard shows an initialising state during setup. + dashboard shows an initializing state during setup.

diff --git a/application/app/components/test-case/TestCaseAiCard.vue b/application/app/components/test-case/TestCaseAiCard.vue index f89eb990..da3f1ea0 100644 --- a/application/app/components/test-case/TestCaseAiCard.vue +++ b/application/app/components/test-case/TestCaseAiCard.vue @@ -139,10 +139,15 @@ const showResult = computed( () => diagnosis.value && (diagnosis.value.status === 'completed' || diagnosis.value.status === 'failed'), ); const showDiagnoseButton = computed( - () => !diagnosis.value || diagnosis.value.status === 'failed' || (diagnosis.value.status === 'running' && isStale(diagnosis.value)), + () => + !diagnosis.value || + diagnosis.value.status === 'failed' || + (diagnosis.value.status === 'running' && isStale(diagnosis.value)), ); /** A fresh 'running' row: a diagnosis is genuinely in flight (this or another session). */ -const showRunning = computed(() => diagnosis.value?.status === 'running' && !isStale(diagnosis.value) && !posting.value); +const showRunning = computed( + () => diagnosis.value?.status === 'running' && !isStale(diagnosis.value) && !posting.value, +); - + @@ -151,11 +153,7 @@ const squareClass = (status: string) => ({

Recent runs (oldest → newest)

- +
-
+
{{ row.text }}
+ :class=" + row.failing + ? 'bg-red-50 dark:bg-red-950/30 text-red-700 dark:text-red-300 font-medium' + : 'text-gray-600 dark:text-gray-400' + " + > + {{ row.text }} +
diff --git a/application/app/demo/db.client.ts b/application/app/demo/db.client.ts index 9575f8a0..aed9f2ef 100644 --- a/application/app/demo/db.client.ts +++ b/application/app/demo/db.client.ts @@ -219,7 +219,7 @@ async function initialize(): Promise { } /** - * Returns the singleton in-browser Drizzle instance, initialising it on + * Returns the singleton in-browser Drizzle instance, initializing it on * first call (fetching the seed SQL and opening IndexedDB). */ export async function getDemoDb(): Promise { diff --git a/application/app/pages/docs.vue b/application/app/pages/docs.vue index 8baf5459..a200ebe1 100644 --- a/application/app/pages/docs.vue +++ b/application/app/pages/docs.vue @@ -4,15 +4,25 @@ const isDemo = config.public.demoMode; const specUrl = isDemo ? '/demo/_openapi.json' : '/_openapi.json'; const container = ref(); +// The interactive reference is loaded from a CDN; a restricted/offline network +// must fall back to a plain link to the raw spec instead of a blank page. +const status = ref<'loading' | 'ready' | 'error'>('loading'); +const SCRIPT_TIMEOUT_MS = 8000; + useHead({ title: 'API Reference — Piwi Dashboard', }); -onMounted(async () => { +onMounted(() => { + const timeout = setTimeout(() => { + if (status.value === 'loading') status.value = 'error'; + }, SCRIPT_TIMEOUT_MS); + const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/@scalar/api-reference'; script.async = true; script.onload = () => { + clearTimeout(timeout); const S = (window as unknown as Record).Scalar as | { createApiReference: (element: HTMLElement, config: Record) => void } | undefined; @@ -27,15 +37,31 @@ onMounted(async () => { 'REST API for storing and querying Playwright test results, traces, failure diagnoses, and project statistics.', }, }); + status.value = 'ready'; + } else { + status.value = 'error'; } }; + script.onerror = () => { + clearTimeout(timeout); + status.value = 'error'; + }; document.head.appendChild(script); });