Skip to content

Scheduler: Multilevel grouping — Remove duplicated separators above the first group in the vertical group panel #87477

Scheduler: Multilevel grouping — Remove duplicated separators above the first group in the vertical group panel

Scheduler: Multilevel grouping — Remove duplicated separators above the first group in the vertical group panel #87477

Workflow file for this run

name: TestCafe tests
concurrency:
group: wf-${{github.event.pull_request.number || github.sha}}-${{github.workflow}}
cancel-in-progress: true
on:
pull_request:
paths-ignore:
- 'apps/**/*.md'
merge_group:
workflow_dispatch:
env:
NX_SKIP_NX_CACHE: ${{ contains(github.event.pull_request.labels.*.name, 'skip-cache') && 'true' || 'false' }}
RUN_TESTS: true
jobs:
check-should-run:
name: Check if tests should run
runs-on: ubuntu-latest
outputs:
should-run: ${{ steps.check.outputs.should-run }}
styles-changed: ${{ steps.changes.outputs.styles }}
steps:
- uses: actions/checkout@v6
- name: Check RUN_TESTS flag
id: check
run: echo "should-run=${{ env.RUN_TESTS }}" >> $GITHUB_OUTPUT
- uses: dorny/paths-filter@v4
id: changes
with:
filters: |
styles:
- 'packages/devextreme-scss/scss/**'
- 'packages/devextreme-scss/icons/**'
- 'packages/devextreme-scss/images/**'
- 'packages/devextreme-scss/fonts/**'
- 'packages/devextreme/js/**/themes/**'
- 'e2e/testcafe-devextreme/helpers/accessibility/**'
build:
name: Build DevExtreme
needs: check-should-run
if: needs.check-should-run.outputs.should-run == 'true'
runs-on: devextreme-shr2
timeout-minutes: 15
steps:
- name: Get sources
uses: actions/checkout@v6
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
- uses: pnpm/action-setup@v6
with:
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v5
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-cache
- name: Lookup nx cache
id: lookup-nx-cache
uses: actions/cache/restore@v5
with:
path: |
.nx/cache
.nx/workspace-data/*.db
.nx/workspace-data/*.db-wal
.nx/workspace-data/*.db-shm
key: ${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}-
${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}-
${{ runner.os }}-nx-v2-
lookup-only: true
- name: Show nx cache status
shell: bash
run: |
echo "Nx cache lookup exact hit: ${{ steps.lookup-nx-cache.outputs.cache-hit }}"
echo "Nx cache primary key: ${{ steps.lookup-nx-cache.outputs.cache-primary-key }}"
echo "Nx cache matched key: ${{ steps.lookup-nx-cache.outputs.cache-matched-key }}"
if [ -z "${{ steps.lookup-nx-cache.outputs.cache-matched-key }}" ]; then
echo "Nx cache lookup: miss"
elif [ "${{ steps.lookup-nx-cache.outputs.cache-hit }}" = "true" ]; then
echo "Nx cache lookup: exact hit"
else
echo "Nx cache lookup: partial hit via restore-keys"
fi
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup nx cache
id: setup-nx-cache
uses: actions/cache@v5
with:
path: |
.nx/cache
.nx/workspace-data/*.db
.nx/workspace-data/*.db-wal
.nx/workspace-data/*.db-shm
key: ${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}-
${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}-
${{ runner.os }}-nx-v2-
- name: Build
shell: bash
env:
NODE_OPTIONS: --max-old-space-size=8192
run: |
pnpm exec nx build devextreme-scss
pnpm exec nx build devextreme -c testing
- name: Show nx cache contents before save
if: ${{ always() }}
shell: bash
run: |
du -sh .nx/cache .nx/workspace-data 2>/dev/null || true
find .nx/cache -type f 2>/dev/null | wc -l | xargs echo "Nx cache files:"
find .nx/workspace-data -maxdepth 1 -type f 2>/dev/null | sed 's#^#Nx workspace-data file: #'
node - <<'NODE'
const fs = require('fs');
const runJsonPath = '.nx/cache/run.json';
if (!fs.existsSync(runJsonPath)) {
console.log('Nx latest run: .nx/cache/run.json is missing');
process.exit(0);
}
const data = JSON.parse(fs.readFileSync(runJsonPath, 'utf8'));
const getCache = () => {
try {
const { NxCache } = require('nx/src/native');
const { workspaceRoot } = require('nx/src/utils/workspace-root');
const { cacheDir } = require('nx/src/utils/cache-directory');
const { getDbConnection } = require('nx/src/utils/db-connection');
return new NxCache(workspaceRoot, cacheDir, getDbConnection());
} catch (error) {
console.log(`Nx cache entry check unavailable: ${error.message}`);
return null;
}
};
const cache = getCache();
const getCacheEntryStatus = (hash) => {
if (!cache) {
return 'unchecked';
}
try {
return cache.get(hash) ? 'present' : 'missing';
} catch (error) {
return `error: ${error.message}`;
}
};
const tasks = (data.tasks ?? []).map((task) => ({
taskId: task.taskId,
hash: task.hash,
cacheStatus: task.cacheStatus,
cacheEntry: getCacheEntryStatus(task.hash),
status: task.status,
}));
console.log(`Nx latest run command: ${data.run?.command ?? '<unknown>'}`);
console.log('Nx latest run tasks:');
console.log(JSON.stringify(tasks, null, 2));
NODE
- name: Zip artifacts
working-directory: ./packages/devextreme
run: |
7z a -tzip -mx3 -mmt2 artifacts.zip artifacts ../devextreme-scss/scss/bundles
- name: Upload build artifacts
uses: actions/upload-artifact@v7
with:
name: devextreme-artifacts
path: ./packages/devextreme/artifacts.zip
retention-days: 1
determine-testcafe-matrix:
runs-on: ubuntu-latest
name: Determine TestCafe matrix
needs: check-should-run
if: needs.check-should-run.outputs.should-run == 'true'
outputs:
matrix: ${{ steps.matrix.outputs.matrix }}
steps:
- name: Build matrix
id: matrix
run: |
base_matrix_start=$(cat <<'JSON'
[
{ "componentFolder": "accessibility", "name": "accessibility" }
]
JSON
)
base_matrix_end=$(cat <<'JSON'
[
{ "componentFolder": "common", "name": "common", "concurrency": 2 },
{ "name": "generic", "theme": "generic.light", "concurrency": 2 },
{ "name": "material", "theme": "material.blue.light", "concurrency": 2 },
{ "name": "material - compact", "theme": "material.blue.light.compact", "concurrency": 2 },
{ "componentFolder": "cardView", "name": "cardView" },
{ "componentFolder": "dataGrid", "name": "dataGrid (1/4)", "indices": "1/4", "cache": true, "concurrency": 6 },
{ "componentFolder": "dataGrid", "name": "dataGrid (2/4)", "indices": "2/4", "cache": true, "concurrency": 6 },
{ "componentFolder": "dataGrid", "name": "dataGrid (3/4)", "indices": "3/4", "cache": true, "concurrency": 6 },
{ "componentFolder": "dataGrid", "name": "dataGrid (4/4)", "indices": "4/4", "cache": true, "concurrency": 6 },
{ "componentFolder": "editors", "name": "editors" },
{ "componentFolder": "navigation", "name": "navigation", "concurrency": 6 },
{ "componentFolder": "scheduler/common", "name": "scheduler / common (1/3)", "indices": "1/3" },
{ "componentFolder": "scheduler/common", "name": "scheduler / common (2/3)", "indices": "2/3" },
{ "componentFolder": "scheduler/common", "name": "scheduler / common (3/3)", "indices": "3/3" },
{ "componentFolder": "scheduler/viewOffset", "name": "scheduler / viewOffset (1/2)", "indices": "1/2" },
{ "componentFolder": "scheduler/viewOffset", "name": "scheduler / viewOffset (2/2)", "indices": "2/2" },
{ "componentFolder": "scheduler/timezones", "name": "scheduler / timezones (Europe/Berlin)", "timezone": "Europe/Berlin" },
{ "componentFolder": "scheduler/timezones", "name": "scheduler / timezones (America/Los_Angeles)", "timezone": "America/Los_Angeles" }
]
JSON
)
style_dependent_matrix=$(cat <<'JSON'
[
{ "componentFolder": "accessibility", "name": "accessibility - fluent.dark", "theme": "fluent.blue.dark" },
{ "componentFolder": "accessibility", "name": "accessibility - material.light", "theme": "material.blue.light" },
{ "componentFolder": "accessibility", "name": "accessibility - material.dark", "theme": "material.blue.dark" }
]
JSON
)
matrix=$(jq -c -n \
--argjson baseStart "$base_matrix_start" \
--argjson baseEnd "$base_matrix_end" \
--argjson styleDependent "$style_dependent_matrix" \
--arg stylesChanged "${{ needs.check-should-run.outputs.styles-changed }}" \
'{ include: ($baseStart + (if $stylesChanged == "true" then $styleDependent else [] end) + $baseEnd) }')
echo "matrix=$matrix" >> $GITHUB_OUTPUT
testcafe:
name: ${{ matrix.name }}${{ !matrix.theme && ' - fluent' || '' }}
if: |
needs.check-should-run.outputs.should-run == 'true' &&
needs.determine-testcafe-matrix.result == 'success' &&
needs.build.result == 'success'
needs: [check-should-run, build, determine-testcafe-matrix]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.determine-testcafe-matrix.outputs.matrix) }}
runs-on: devextreme-shr2
timeout-minutes: 45
steps:
- name: Set machine timezone
run: |
sudo ln -sf "/usr/share/zoneinfo/$([ "${{ matrix.timezone }}" != "" ] && echo "${{ matrix.timezone }}" || echo "GMT")" /etc/localtime
date
- name: Get sources
uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v8
with:
name: devextreme-artifacts
path: ./packages/devextreme
- name: Unpack artifacts
working-directory: ./packages/devextreme
run: 7z x artifacts.zip -aoa
- name: Setup Chrome
uses: ./.github/actions/setup-chrome
with:
chrome-version: '149.0.7827.114'
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
- uses: pnpm/action-setup@v6
with:
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache/restore@v5
name: Restore pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-cache
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check runner resources
run: |
echo "CPU cores: $(nproc)"
echo "Memory: $(free -h)"
echo "Disk: $(df -h)"
- name: Run TestCafe tests
working-directory: ./e2e/testcafe-devextreme
env:
NODE_OPTIONS: --max-old-space-size=8192
run: |
if [ "${{ matrix.theme }}" != "" ]; then
THEME="--theme ${{ matrix.theme }}"
else
THEME="--theme fluent.blue.light"
fi
[ "${{ matrix.componentFolder }}" != "" ] && COMPONENT_FOLDER="--componentFolder ${{ matrix.componentFolder }}"
[ "${{ matrix.indices }}" != "" ] && INDICES="--indices ${{ matrix.indices }}"
[ "${{ matrix.concurrency }}" != "" ] && CONCURRENCY="--concurrency ${{ matrix.concurrency }}"
[ "${{ matrix.platform }}" != "" ] && PLATFORM="--platform ${{ matrix.platform }}"
[ "${{ matrix.cache }}" == "true" ] && CACHE="--cache true"
all_args="--browsers=chrome:devextreme-shr2 $COMPONENT_FOLDER $CONCURRENCY $INDICES $PLATFORM $THEME $CACHE"
echo "$all_args"
pnpm run test $all_args
- name: Sanitize job name
if: ${{ always() }}
run: echo "JOB_NAME=$(echo "${{ matrix.name }}" | tr '/' '-')" >> $GITHUB_ENV
- name: Copy compared screenshot artifacts
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: compared-screenshots-${{ env.JOB_NAME }}
path: ${{ github.workspace }}/e2e/testcafe-devextreme/artifacts/compared-screenshots/**/*
if-no-files-found: ignore
- name: Copy failed test artifacts
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: failed-tests-${{ env.JOB_NAME }}
path: ${{ github.workspace }}/e2e/testcafe-devextreme/artifacts/failedtests/**/*
if-no-files-found: ignore
merge-artifacts:
runs-on: ubuntu-latest
needs: testcafe
if: ${{ always() }}
steps:
- name: Merge screenshot artifacts
uses: actions/upload-artifact/merge@v7
continue-on-error: true
with:
name: compared-screenshots
pattern: compared-screenshots-*
delete-merged: true
- name: Merge failed test artifacts
uses: actions/upload-artifact/merge@v7
continue-on-error: true
with:
name: failed-tests
pattern: failed-tests-*
delete-merged: true
notify:
runs-on: ubuntu-latest
name: Send notifications
needs: [build, testcafe]
if: github.event_name != 'pull_request' && contains(needs.*.result, 'failure')
steps:
- uses: actions/checkout@v6
- uses: DevExpress/github-actions/send-teams-notification@v1
with:
hook_url: ${{secrets.TEAMS_ALERT}}
bearer_token: ${{secrets.GITHUB_TOKEN}}
specific_repo: DevExpress/DevExtreme