Compare commits

...
Author SHA1 Message Date
dependabot[bot] 90d0f93f75 build(deps): Bump the github-actions group with 3 updates
Bumps the github-actions group with 3 updates: [QRun-IO/qqq-orb/.github/workflows/next-ci.yml](https://github.com/qrun-io/qqq-orb), [QRun-IO/qqq-orb/.github/workflows/next-acceptance.yml](https://github.com/qrun-io/qqq-orb) and [QRun-IO/qqq-orb/.github/workflows/next-dashboard-image.yml](https://github.com/qrun-io/qqq-orb).


Updates `QRun-IO/qqq-orb/.github/workflows/next-ci.yml` from 0f33ddadf88b9a1b940b8883782fe2000ee1d2bd to 1c389210e6dfda1630d053c07ae4dae523dd5576
- [Release notes](https://github.com/qrun-io/qqq-orb/releases)
- [Changelog](https://github.com/QRun-IO/qqq-orb/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/qrun-io/qqq-orb/compare/0f33ddadf88b9a1b940b8883782fe2000ee1d2bd...1c389210e6dfda1630d053c07ae4dae523dd5576)

Updates `QRun-IO/qqq-orb/.github/workflows/next-acceptance.yml` from 0f33ddadf88b9a1b940b8883782fe2000ee1d2bd to 1c389210e6dfda1630d053c07ae4dae523dd5576
- [Release notes](https://github.com/qrun-io/qqq-orb/releases)
- [Changelog](https://github.com/QRun-IO/qqq-orb/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/qrun-io/qqq-orb/compare/0f33ddadf88b9a1b940b8883782fe2000ee1d2bd...1c389210e6dfda1630d053c07ae4dae523dd5576)

Updates `QRun-IO/qqq-orb/.github/workflows/next-dashboard-image.yml` from 0f33ddadf88b9a1b940b8883782fe2000ee1d2bd to 1c389210e6dfda1630d053c07ae4dae523dd5576
- [Release notes](https://github.com/qrun-io/qqq-orb/releases)
- [Changelog](https://github.com/QRun-IO/qqq-orb/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/qrun-io/qqq-orb/compare/0f33ddadf88b9a1b940b8883782fe2000ee1d2bd...1c389210e6dfda1630d053c07ae4dae523dd5576)

---
updated-dependencies:
- dependency-name: QRun-IO/qqq-orb/.github/workflows/next-ci.yml
  dependency-version: 1c389210e6dfda1630d053c07ae4dae523dd5576
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: QRun-IO/qqq-orb/.github/workflows/next-acceptance.yml
  dependency-version: 1c389210e6dfda1630d053c07ae4dae523dd5576
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: QRun-IO/qqq-orb/.github/workflows/next-dashboard-image.yml
  dependency-version: 1c389210e6dfda1630d053c07ae4dae523dd5576
  dependency-type: direct:production
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-28 05:18:16 +00:00
james.maesandCodex 9756b9d93b ci: adopt managed orb 0.8 patch track (#193)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 16:20:56 -05:00
james.maesandCodex f205992878 ci: adopt shared orb 0.7.17 (#192)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 14:37:37 -05:00
james.maesandCodex 5f503fa7e9 test: disambiguate async help from process search results (#191)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 09:15:38 -05:00
james.maesandCodex 09828ab1cc ci: adopt central orb 0.7.12 and stable gates (#190)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 03:49:28 -05:00
james.maesandCodex c39748b1a0 test: stabilize widget layout and report reload acceptance (#189)
* test: assert widget layouts at container breakpoints

Co-Authored-By: Codex <noreply@openai.com>

* test: wait for saved report readiness before reloading

Co-Authored-By: Codex <noreply@openai.com>

---------

Co-authored-by: Codex <noreply@openai.com>
2026-10-21 03:08:45 -05:00
james.maesandCodex 829ef6b384 ci: align canonical shared orb workers (#188)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 01:49:08 -05:00
james.maesandCodex a94e1e39c8 ci: adopt shared orb 0.7.11 (#187)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 01:12:27 -05:00
james.maesandCodex 9bea8cc63f ci: centralize workflows in QQQ orb (#185)
Co-authored-by: Codex <noreply@openai.com>
2026-10-20 20:04:31 -05:00
10 changed files with 69 additions and 598 deletions

No files matched your search

+10 -76
View File
@@ -1,80 +1,14 @@
version: 2.1
setup: true
orbs:
qqq-orb: kingsrook/qqq-orb@0.6
# ─── Jobs ────────────────────────────────────────────────────────────────────
# The dashboard jar (static export under next-dashboard/) is published to Maven
# Central on release tags, with the same orb commands and credentials as the
# Material Dashboard. QApplicationJavalinServer serves it at / when it is present.
commands:
configure-package-install:
steps:
- run:
name: Keep package installation unattended
command: |
# These build machines are discarded after the job; avoid the interactive needrestart prompt.
sudo sed -i 's|http://security.ubuntu.com/ubuntu|https://archive.ubuntu.com/ubuntu|g' /etc/apt/sources.list
sudo install -d /etc/needrestart/conf.d
printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null
jobs:
publish_dashboard_jar:
executor: qqq-orb/java
steps:
- configure-package-install
- checkout
- qqq-orb/install_java:
install_node: true
node_version: "22"
- run:
name: Verify the immutable release version
command: |
release_version="${CIRCLE_TAG#v}"
frontend_version=$(node -p "require('./package.json').version")
maven_version=$(mvn -B -q help:evaluate -Dexpression=project.version -DforceStdout)
test "$frontend_version" = "$release_version"
test "$maven_version" = "$release_version"
- run:
name: Build the static export
command: |
corepack enable
pnpm install --frozen-lockfile
pnpm typecheck
pnpm lint
pnpm test
pnpm build:export
- qqq-orb/mvn_jar_deploy
# ─── Workflows ───────────────────────────────────────────────────────────────
# Trunk-based: main is the only long-lived branch (see CONTRIBUTING.md).
# test_only → every branch except main (the PR check ci/circleci: qqq-orb/node_app_test_only)
# publish_release → main builds; v* version tags publish the dashboard jar
qqq-orb: kingsrook/qqq-orb@0.8
workflows:
test_only:
setup:
jobs:
- qqq-orb/node_app_test_only:
filters:
branches:
ignore: main
tags:
only: []
publish_release:
jobs:
- qqq-orb/node_app_build:
filters:
branches:
only: [ main ]
tags:
only: [ /v.*/ ]
- publish_dashboard_jar:
context: [qqq-maven-registry-credentials]
filters:
branches:
ignore: /.*/
tags:
only: /^v[0-9]+\.[0-9]+\.[0-9]+(-RC\.[0-9]+)?$/
- qqq-orb/generate_pipeline:
orb_version: '0.8'
filters:
branches:
ignore: /.*/
tags:
only: /.*/
-281
View File
@@ -1,281 +0,0 @@
name: Real-backend acceptance
# One production build feeds independent, serial-worker backends. Require "acceptance gate".
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
inputs:
qqq_ref:
description: QRun-IO/qqq branch or tag to build the sample backend from
required: false
permissions:
contents: read
concurrency:
group: acceptance-${{ github.ref }}
cancel-in-progress: true
env:
QQQ_REF: ${{ inputs.qqq_ref || vars.QQQ_ACCEPTANCE_REF || 'develop' }}
NEXT_TELEMETRY_DISABLED: '1'
MAVEN_ARGS: -Dmaven.wagon.http.retryHandler.count=5 -Daether.transport.http.retryHandler.count=5 -Daether.connector.http.retryHandler.count=5
jobs:
plan:
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
mode: ${{ steps.select.outputs.mode }}
matrix: ${{ steps.select.outputs.matrix }}
specs: ${{ steps.select.outputs.specs }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Select projects, shards and changed areas
id: select
run: node scripts/acceptance-select.mjs
build:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: QRun-IO/qqq
ref: ${{ env.QQQ_REF }}
path: qqq-backend
- name: Resolve QQQ source revision
id: qqq
run: echo "sha=$(git -C qqq-backend rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '21'
cache: maven
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: qqq-cache
with:
path: ~/.m2/repository/com/kingsrook
key: qqq-source-${{ runner.os }}-java21-v1-${{ steps.qqq.outputs.sha }}
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Bundled UI API document matches the server's (src/lib/api/openapi/qqq-v1.json)
run: node scripts/check-ui-api-spec.mjs --server-spec qqq-backend/qqq-middleware-javalin/src/main/resources/openapi/v1/openapi.yaml
- name: Build QQQ from source
if: steps.qqq-cache.outputs.cache-hit != 'true'
run: bash scripts/ci/retry.sh 4 mvn -B -q -f qqq-backend/pom.xml install -DskipTests -Dspotbugs.skip=true -Dpmd.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true
- name: Build and install the dashboard jar (static export)
run: |
# Equivalent to build:jar, with retries around its Maven invocation too.
pnpm build:export
bash scripts/ci/retry.sh 4 mvn -B -q clean package
bash scripts/ci/retry.sh 4 mvn -B -q install
# Failed-attempt Maven stdout must not become part of the version value.
bash scripts/ci/retry.sh 4 mvn -B -q help:evaluate -Dexpression=project.version -Doutput="$RUNNER_TEMP/acceptance-next-version"
next_version=$(cat "$RUNNER_TEMP/acceptance-next-version")
echo "NEXT_JAR_VERSION=$next_version" >> "$GITHUB_ENV"
- name: Build the sample application jar
working-directory: qqq-backend
run: |
# QQQ checkstyle/pmd paths are relative to this checkout.
bash "$GITHUB_WORKSPACE/scripts/ci/retry.sh" 4 mvn -B -q -f qqq-sample-project/pom.xml package -DskipTests -Djacoco.skip=true \
-Dqqq.frontend.next.version="$NEXT_JAR_VERSION"
- name: Stage runtime artifacts
run: |
sample_jars=(qqq-backend/qqq-sample-project/target/*-jar-with-dependencies.jar)
test "${#sample_jars[@]}" -eq 1
sample_version="${sample_jars[0]##*/}"
sample_version="${sample_version#qqq-sample-project-}"
sample_version="${sample_version%-jar-with-dependencies.jar}"
mkdir -p test-results/acceptance-jars
cp "${sample_jars[0]}" test-results/acceptance-jars/
# javac and the security fixture need the API JAR in addition to the fat sample JAR.
cp "$HOME/.m2/repository/com/kingsrook/qqq/qqq-middleware-api/$sample_version/qqq-middleware-api-$sample_version.jar" \
test-results/acceptance-jars/qqq-middleware-api.jar
tar -cf test-results/acceptance-build.tar out test-results/acceptance-jars
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: acceptance-build
path: test-results/acceptance-build.tar
if-no-files-found: error
retention-days: 30
shards:
name: acceptance ${{ matrix.project }} ${{ matrix.shard }}/${{ matrix.total }}
needs: [plan, build]
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}
runs-on: ubuntu-24.04
timeout-minutes: 40
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '21'
cache: maven
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Installed Playwright version
id: playwright
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: browser-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright.outputs.version }}-${{ matrix.browser }}
- run: pnpm exec playwright install-deps ${{ matrix.browser }}
- if: steps.browser-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install ${{ matrix.browser }}
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: acceptance-build
path: test-results
- name: Restore runtime artifacts
run: |
tar -xf test-results/acceptance-build.tar
echo "QQQ_SAMPLE_JAR=$(ls "$PWD"/test-results/acceptance-jars/*-jar-with-dependencies.jar)" >> "$GITHUB_ENV"
echo "QQQ_MIDDLEWARE_API_JAR=$PWD/test-results/acceptance-jars/qqq-middleware-api.jar" >> "$GITHUB_ENV"
- name: Run shard
env:
QQQ_ACCEPTANCE_BROWSERS: ${{ matrix.project }}
QQQ_ACCEPTANCE_SPEC_PATHS: ${{ needs.plan.outputs.specs }}
QQQ_ACCEPTANCE_BLOB: '1'
# Environment-selected projects must not produce colliding default blob names.
QQQ_ACCEPTANCE_BLOB_NAME: report-${{ matrix.project }}-${{ matrix.shard }}.zip
QQQ_ACCEPTANCE_TRANSPORT_TRACE: '1'
QQQ_ACCEPTANCE_BROWSER_TRACE: ${{ (matrix.project == 'webkit' || matrix.project == 'mobile' || matrix.project == 'tablet') && 'navigation' || '0' }}
DEBUG: pw:browser
run: node scripts/acceptance.mjs --skip-build --shard=${{ matrix.shard }}/${{ matrix.total }}
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: acceptance-blob-${{ matrix.project }}-${{ matrix.shard }}
path: test-results/acceptance/blob-report/
if-no-files-found: error
retention-days: 30
gate:
name: acceptance gate
if: always()
needs: [plan, build, shards]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: acceptance-blob-*
path: test-results/acceptance/blobs
merge-multiple: true
- name: Merge shard reports
env:
PLAYWRIGHT_JSON_OUTPUT_NAME: test-results/acceptance/report.json
PLAYWRIGHT_HTML_OUTPUT_DIR: test-results/acceptance/html
PLAYWRIGHT_HTML_OPEN: never
run: npx playwright merge-reports --reporter=json,html test-results/acceptance/blobs
- name: Evaluate acceptance matrix
if: always()
env:
ACCEPTANCE_MODE: ${{ needs.plan.outputs.mode }}
run: |
args=()
if [[ "$ACCEPTANCE_MODE" == pr ]]; then args+=(--partial); fi
node scripts/acceptance-gate.mjs "${args[@]}"
- name: Require every planned shard and successful build
if: always()
env:
PLAN_RESULT: ${{ needs.plan.result }}
BUILD_RESULT: ${{ needs.build.result }}
SHARDS_RESULT: ${{ needs.shards.result }}
PLANNED_MATRIX: ${{ needs.plan.outputs.matrix }}
run: |
test "$PLAN_RESULT" = success
test "$BUILD_RESULT" = success
test "$SHARDS_RESULT" = success
node --input-type=module -e 'import { existsSync } from "node:fs"; for (const { project, shard } of JSON.parse(process.env.PLANNED_MATRIX).include) { const file = "test-results/acceptance/blobs/report-" + project + "-" + shard + ".zip"; if (!existsSync(file)) throw new Error("Missing shard report: " + file); }'
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: acceptance
path: test-results/acceptance/
if-no-files-found: error
retention-days: 30
quarantine:
name: acceptance quarantine (chromium)
needs: [plan, build]
if: needs.plan.outputs.mode == 'full'
continue-on-error: true
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '21'
cache: maven
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Installed Playwright version
id: playwright
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: browser-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright.outputs.version }}-chromium
- run: pnpm exec playwright install-deps chromium
- if: steps.browser-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install chromium
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: acceptance-build
path: test-results
- name: Restore runtime artifacts
run: |
tar -xf test-results/acceptance-build.tar
echo "QQQ_SAMPLE_JAR=$(ls "$PWD"/test-results/acceptance-jars/*-jar-with-dependencies.jar)" >> "$GITHUB_ENV"
echo "QQQ_MIDDLEWARE_API_JAR=$PWD/test-results/acceptance-jars/qqq-middleware-api.jar" >> "$GITHUB_ENV"
- name: Run quarantined scenarios
env:
QQQ_ACCEPTANCE_BROWSERS: chromium
QQQ_ACCEPTANCE_QUARANTINE: only
QQQ_ACCEPTANCE_TRANSPORT_TRACE: '1'
QQQ_ACCEPTANCE_BROWSER_TRACE: '0'
DEBUG: pw:browser
run: node scripts/acceptance.mjs --skip-build
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: acceptance-quarantine
path: test-results/acceptance/
retention-days: 30
+36 -153
View File
@@ -1,158 +1,41 @@
name: CI
# Fast PR checks (QRun-IO/qqq#709, bundle budget #710). The real-backend gate, including the
# runtime performance budgets (specs/performance), runs in acceptance.yml.
# Each job installs on its own so they run in parallel; none needs a backend.
on:
name: Next centralized CI
'on':
pull_request:
types:
- opened
- synchronize
- reopened
- labeled
- unlabeled
push:
branches: [main]
branches:
- main
tags:
- v*
workflow_dispatch:
inputs:
qqq_ref:
description: QQQ source ref for acceptance
type: string
required: false
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
NEXT_TELEMETRY_DISABLED: '1'
STORYBOOK_DISABLE_TELEMETRY: '1'
jobs:
unit:
name: Typecheck, lint, unit tests and coverage
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: pnpm
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm lint
- name: Unit tests with src/lib coverage thresholds (vitest.config.ts)
run: pnpm test:coverage
e2e:
name: Mocked e2e (Playwright, chromium)
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: pnpm
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
- name: Installed Playwright version
id: playwright
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: browser-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright.outputs.version }}-chromium
- run: pnpm exec playwright install-deps chromium
- if: steps.browser-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install chromium
- name: Production build
run: pnpm build
- name: Mocked e2e suite
env:
QQQ_E2E_SERVER: production
run: pnpm test:e2e
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: failure()
with:
name: e2e-report
path: |
playwright-report/
test-results/
retention-days: 14
collaboration-e2e:
name: Collaboration e2e (MSW; chromium, firefox, webkit, mobile, tablet)
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: pnpm
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
- name: Installed Playwright version
id: playwright
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: browser-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright.outputs.version }}-all
- run: pnpm exec playwright install-deps chromium firefox webkit
- if: steps.browser-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install chromium firefox webkit
- name: Collaboration suite (mock API, every browser project)
run: pnpm exec playwright test --config playwright.collaboration.config.ts
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: collaboration-e2e
path: |
test-results/collaboration/
test-results/collab-shots/
retention-days: 14
storybook:
name: Storybook build
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: pnpm
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
- run: pnpm build-storybook --quiet
performance:
name: Bundle budget (static export)
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: corepack enable
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: pnpm
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
- name: Static export
run: pnpm build:export
- name: First-load JS per route, total JS and largest chunk against perf-budget.json
run: pnpm perf:budget
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: bundle-budget
path: test-results/performance/bundle.json
retention-days: 30
ci:
if: (github.event_name == 'pull_request' && !contains(fromJSON('["labeled","unlabeled"]'),
github.event.action)) || (github.event_name == 'push' && github.ref == 'refs/heads/main')
uses: QRun-IO/qqq-orb/.github/workflows/next-ci.yml@1c389210e6dfda1630d053c07ae4dae523dd5576
acceptance:
if: github.event_name != 'pull_request' || !contains(fromJSON('["labeled","unlabeled"]'),
github.event.action) || github.event.label.name == 'full-acceptance'
uses: QRun-IO/qqq-orb/.github/workflows/next-acceptance.yml@1c389210e6dfda1630d053c07ae4dae523dd5576
with:
qqq_ref: ${{ inputs.qqq_ref || '' }}
dashboard-image:
if: (github.event_name == 'pull_request' && !contains(fromJSON('["labeled","unlabeled"]'),
github.event.action)) || (github.event_name == 'push' && startsWith(github.ref,
'refs/tags/v'))
permissions:
contents: read
packages: write
uses: QRun-IO/qqq-orb/.github/workflows/next-dashboard-image.yml@1c389210e6dfda1630d053c07ae4dae523dd5576
-75
View File
@@ -1,75 +0,0 @@
name: Dashboard image
on:
pull_request:
push:
tags: ['v*']
permissions:
contents: read
env:
IMAGE: ghcr.io/qrun-io/qqq-frontend-next
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
if: github.event_name == 'push'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
with:
context: .
file: docker/quickstart/Dockerfile
platforms: linux/${{ matrix.arch }}
push: ${{ github.event_name == 'push' }}
load: ${{ github.event_name == 'pull_request' }}
tags: ${{ env.IMAGE }}:${{ github.sha }}-${{ matrix.arch }}
cache-from: type=gha,scope=dashboard-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=dashboard-${{ matrix.arch }}
- name: Verify runnable dashboard
if: github.event_name == 'pull_request'
env:
TAG: ${{ env.IMAGE }}:${{ github.sha }}-${{ matrix.arch }}
run: |
container=$(docker run -d --rm -p 127.0.0.1:3000:3000 "$TAG")
trap 'docker rm -f "$container"' EXIT
curl -fsS --max-time 3 --retry 30 --retry-delay 1 --retry-all-errors \
--retry-max-time 90 http://127.0.0.1:3000/app/person >/dev/null \
|| { docker logs "$container"; exit 1; }
publish:
if: github.event_name == 'push'
needs: build
runs-on: ubuntu-24.04
permissions:
packages: write
steps:
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish versioned multi-architecture image
run: |
docker buildx imagetools create \
--tag "$IMAGE:${GITHUB_REF_NAME#v}" \
"$IMAGE:$GITHUB_SHA-amd64" "$IMAGE:$GITHUB_SHA-arm64"
+3
View File
@@ -0,0 +1,3 @@
profile: frontend-next
orb_version: '0.8'
default_branch: main
@@ -136,10 +136,12 @@ test.describe('command palette and search', () => {
await page.locator('[data-qqq-id="button-search"]').click()
const dialog = page.getByRole('dialog', { name: 'Command palette' })
await dialog.getByRole('combobox').fill('clone')
const clone = dialog.getByRole('option', { name: /Clone People/ })
// Help arrives asynchronously and can share the process label; it is not a local page match.
await expect(dialog.locator('[data-qqq-id="help-result-process:clonePeople;slot:help"]')).toBeVisible()
const clone = dialog.locator('[data-qqq-id="command-item-clonePeople"]')
await expect(clone).toHaveCount(1)
await expect(clone).toContainText('People App')
await expect(dialog.getByRole('option')).toHaveCount(2)
await expect(dialog.locator('[data-qqq-id^="command-item-"]')).toHaveCount(1)
await expect(dialog.locator('[data-qqq-id="command-search-all"]')).toContainText('Search everything for “clone”')
await expectTouchTargets(dialog)
await expect(clone).toHaveAttribute('aria-selected', 'true')
@@ -9,7 +9,7 @@
import type { Page } from '@playwright/test'
import { expect, open, test } from '../../support/fixtures'
import { expectTouchReady } from '../../support/touch'
import { expectLoaded, isLargeLayout, widget, widgetBody } from './widget-support'
import { expectLoaded, widget, widgetBody } from './widget-support'
/** Records every widget data request the page makes. */
function widgetRequests(page: Page): string[] {
@@ -37,13 +37,14 @@ test.describe('as administrator', () => {
await expect(parent.locator('[data-qqq-id="widget-label-accChildA"]')).toHaveText('Child A')
const a = await parent.locator('[data-qqq-id="widget-accChildA"]').boundingBox()
const b = await parent.locator('[data-qqq-id="widget-accChildB"]').boundingBox()
if (await isLargeLayout(page)) {
const full = await parent.boundingBox()
const rem = await page.evaluate(() => parseFloat(getComputedStyle(document.documentElement).fontSize))
if (full!.width >= 40 * rem) {
// children sit side by side (gridColumns 6 of 12)
expect(Math.round(a!.y)).toBe(Math.round(b!.y))
expect(b!.x).toBeGreaterThan(a!.x)
} else {
// below the large breakpoint the grid is one column: each child spans the parent, stacked
const full = await parent.boundingBox()
// Below the two-column container breakpoint each child spans the parent, stacked.
expect(Math.round(a!.width)).toBe(Math.round(full!.width))
expect(Math.round(b!.width)).toBe(Math.round(full!.width))
expect(Math.round(b!.x)).toBe(Math.round(a!.x))
@@ -9,7 +9,7 @@
import { readChartLabelBoxes, type ChartLabelBox } from '../../support/chart-label-geometry'
import { expect, open, test } from '../../support/fixtures'
import { expectNoHorizontalScroll, expectTouchReady } from '../../support/touch'
import { chartTable, expectLoaded, isLargeLayout, widget, widgetPayload } from './widget-support'
import { chartTable, expectLoaded, widget, widgetPayload } from './widget-support'
const DASHBOARD = '/app/SampleWidgetsDashboard'
@@ -33,10 +33,15 @@ test.describe('sample widgets dashboard', () => {
const third = await page.locator('[data-qqq-id="widget-grid-item-SampleMultiStatisticsWidget"]').boundingBox()
// gridColumns 12 spans the grid
expect(Math.round(composite!.width)).toBe(Math.round(full!.width))
if (await isLargeLayout(page)) {
const rem = await page.evaluate(() => parseFloat(getComputedStyle(document.documentElement).fontSize))
if (full!.width >= 60 * rem) {
// gridColumns 4 takes a third of it (less the gaps)
expect(third!.width / full!.width).toBeGreaterThan(0.3)
expect(third!.width / full!.width).toBeLessThan(0.34)
} else if (full!.width >= 40 * rem) {
// Tablet-sized containers use two columns, preserving room for each widget.
expect(third!.width / full!.width).toBeGreaterThan(0.48)
expect(third!.width / full!.width).toBeLessThan(0.5)
} else {
// on phones and tablets every widget takes the full width, one per row, in declared order
const boxes = await items.evaluateAll((nodes) => nodes.map((node) => node.getBoundingClientRect().toJSON() as DOMRect))
@@ -474,6 +474,10 @@ test('[WID-073] create a report from a query, switch tables, save, reopen and re
const stored = (await sqlRows(backend, `select table_name, columns_json from saved_report where id = ${Number(id)}`))[0]
expect(stored.table_name).toBe('audit')
expect(JSON.parse(stored.columns_json).columns.some((column: { name: string }) => ['__check__', 'code', 'shelf', 'name'].includes(column.name))).toBe(false)
// Reopen a fully loaded report; reloading during preview reads interrupts WebKit's XHRs.
await expectLoaded(page, 'reportSetupWidget')
await expect(byId(page, 'filter-preview-refresh-reportSetupWidget')).toBeEnabled()
await expect(page.locator('[data-widget-type][aria-busy="true"]').filter({ visible: true })).toHaveCount(0)
await page.reload()
await expect(byId(page, 'report-columns-reportSetupWidget').locator('li')).toHaveText(['Id'])
await recordAction(page, 'Render Report')
@@ -67,11 +67,6 @@ export async function isPhoneLayout(page: Page): Promise<boolean> {
return page.evaluate(() => matchMedia('(max-width: 767.98px)').matches)
}
/** Whether the page shows the desktop dashboard grid (Tailwind's `lg` breakpoint, 1024 px). */
export async function isLargeLayout(page: Page): Promise<boolean> {
return page.evaluate(() => matchMedia('(min-width: 1024px)').matches)
}
/**
* Opens a record view and shows every section: on a phone the sections are an accordion with
* only the first one open, so widgets in the others render once their section is expanded.