Skip to content
Merged
97 changes: 91 additions & 6 deletions .github/workflows/tests-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,62 @@ on:
- 'tsconfig.json'

jobs:
component-tests:
if: ${{ !github.event.pull_request.draft }}
timeout-minutes: 15
runs-on: blacksmith-8vcpu-ubuntu-2404
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
env:
CYPRESS_INSTALL_BINARY: '0'
- name: Cache Cypress binary
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/Cypress
key: cypress-binary-${{ runner.os }}-${{ runner.arch }}-component-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
cypress-binary-${{ runner.os }}-${{ runner.arch }}-
- name: Install Cypress binary if missing
working-directory: packages/app
run: pnpm exec cypress verify || pnpm exec cypress install
- name: Build app
run: pnpm build
env:
E2E_FIXTURES: '1'
- name: Run component tests
working-directory: packages/app
run: pnpm exec cypress run --component --browser chrome
env:
CI: true
- name: Upload Cypress artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: cypress-component-report
path: |
packages/app/cypress/screenshots/
retention-days: 1

cypress:
if: ${{ !github.event.pull_request.draft }}
timeout-minutes: 15
runs-on: ubuntu-latest
runs-on: blacksmith-8vcpu-ubuntu-2404
permissions:
contents: read
strategy:
fail-fast: false
matrix:
browser: [chrome, firefox]
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5
Expand Down Expand Up @@ -54,27 +100,66 @@ jobs:
run: pnpm build
env:
E2E_FIXTURES: '1'
- name: Run component tests
working-directory: packages/app
run: pnpm exec cypress run --component
- name: Select shard specs
id: shard-specs
env:
CI: true
SHARD_INDEX: ${{ matrix.shard }}
SHARD_TOTAL: 4
run: |
node <<'EOF'
const fs = require('node:fs');
const path = require('node:path');

const rootDir = path.resolve('packages/app/cypress/e2e');
const shardIndex = Number(process.env.SHARD_INDEX);
const shardTotal = Number(process.env.SHARD_TOTAL);
const outputFile = process.env.GITHUB_OUTPUT;

function collectSpecs(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...collectSpecs(fullPath));
} else if (entry.isFile() && entry.name.endsWith('.cy.ts')) {
files.push(path.relative(path.resolve('packages/app'), fullPath).replaceAll('\\', '/'));
}
}
return files;
}

const allSpecs = collectSpecs(rootDir).sort();
const assignedSpecs = allSpecs.filter((_, index) => index % shardTotal === shardIndex - 1);
const specsCsv = assignedSpecs.join(',');

fs.appendFileSync(outputFile, `specs=${specsCsv}\n`);
fs.appendFileSync(outputFile, `spec_count=${assignedSpecs.length}\n`);
fs.appendFileSync(outputFile, `total_specs=${allSpecs.length}\n`);
console.log(`[Shard ${shardIndex}/${shardTotal}] Assigned ${assignedSpecs.length}/${allSpecs.length} specs`);
for (const spec of assignedSpecs) console.log(` - ${spec}`);
EOF
- name: Run integration tests
if: ${{ steps.shard-specs.outputs.spec_count != '0' }}
uses: cypress-io/github-action@c495c3ddffba403ba11be95fffb67e25203b3799 # v7.1.10
with:
working-directory: packages/app
install: false
start: pnpm start
wait-on: 'http://localhost:3000'
browser: ${{ matrix.browser }}
spec: ${{ steps.shard-specs.outputs.specs }}
env:
CI: true
E2E_FIXTURES: '1'
- name: Skip empty shard
if: ${{ steps.shard-specs.outputs.spec_count == '0' }}
run: echo "No specs assigned for this shard; skipping Cypress run."
- name: Upload Cypress artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: cypress-${{ matrix.browser }}-report
name: cypress-${{ matrix.browser }}-shard-${{ matrix.shard }}-report
path: |
packages/app/cypress/screenshots/
retention-days: 1
2 changes: 1 addition & 1 deletion .github/workflows/tests-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
vitest:
if: ${{ !github.event.pull_request.draft }}
timeout-minutes: 10
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
steps:
Expand Down
59 changes: 45 additions & 14 deletions packages/app/cypress/e2e/drill-down-trend.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
* Users double-click scatter chart data points to track configs over time,
* which opens a modal dialog with TrendCharts.
*/
describe('Drill-Down Trend Chart Modal', () => {
before(() => {
cy.window().then((win) => {
const visitInferenceWithDataReady = () => {
cy.visit('/inference', {
onBeforeLoad(win) {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
cy.visit('/inference');
// Wait for scatter graph to render with data points
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg .dot-group')
.should('have.length.greaterThan', 0);
},
});
// Wait for scatter graph to render with data points
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg .dot-group')
.should('have.length.greaterThan', 0);
};

describe('Drill-Down Trend Chart Modal', () => {
beforeEach(() => {
visitInferenceWithDataReady();
});

it('modal is not visible initially', () => {
Expand All @@ -31,28 +36,54 @@ describe('Drill-Down Trend Chart Modal', () => {
});

it('shows tracked config badge after double-clicking a point', () => {
// Modal is still open from previous test
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg .dot-group')
.first()
.dblclick({ force: true });
cy.contains('Performance Over Time').should('be.visible');
cy.get('[data-testid="tracked-config-badge"]').should('have.length.at.least', 1);
});

it('shows two trend chart SVGs (Y-axis and X-axis metrics) in the modal', () => {
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg .dot-group')
.first()
.dblclick({ force: true });
cy.contains('Performance Over Time').should('be.visible');
cy.get('[role="dialog"]').find('[data-testid="trend-chart-svg"]').should('have.length', 2);
});

it('shows the helper text about double-clicking points', () => {
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg .dot-group')
.first()
.dblclick({ force: true });
cy.contains('Performance Over Time').should('be.visible');
cy.contains(
'Double-click points on the scatter chart to track configurations over time',
).should('be.visible');
});

it('tracked point gets a visual ring indicator on the scatter chart', () => {
it('tracking a point is reflected in modal state', () => {
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg .tracked-ring')
.should('have.length.at.least', 1);
.find('svg .dot-group')
.first()
.dblclick({ force: true });
cy.contains('Performance Over Time').should('be.visible');
cy.get('[data-testid="tracked-config-badge"]').should('have.length.at.least', 1);
});

it('removing a config badge via its X button removes just that config', () => {
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg .dot-group')
.first()
.dblclick({ force: true });
cy.contains('Performance Over Time').should('be.visible');
// Click the X button on the config badge — with only one tracked, modal closes
cy.get('[data-testid="tracked-config-badge"]').first().find('button').click();
cy.contains('Performance Over Time').should('not.exist');
Expand Down
25 changes: 13 additions & 12 deletions packages/app/cypress/e2e/historical-trends.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
* Tests for the "Historical Trends" tab.
* Shows interpolated GPU performance over time at a user-selected interactivity level.
*/
describe('Historical Trends Tab', () => {
before(() => {
cy.window().then((win) => {
const visitHistoricalWithSetup = () => {
cy.visit('/historical', {
onBeforeLoad(win) {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
cy.visit('/historical');
cy.get('[data-testid="historical-trends-display"]').should('be.visible');
},
});
cy.get('[data-testid="historical-trends-display"]').should('be.visible');
};

describe('Historical Trends Tab', () => {
beforeEach(() => {
visitHistoricalWithSetup();
});

it('renders the Historical Trends tab content', () => {
Expand All @@ -35,12 +40,8 @@ describe('Historical Trends Tab', () => {
});

describe('Historical Trends — Content & Interactions', () => {
before(() => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
cy.visit('/historical');
cy.get('[data-testid="historical-trends-display"]').should('be.visible');
beforeEach(() => {
visitHistoricalWithSetup();
});

it('renders SVG trend line paths after data loads', () => {
Expand Down
Loading
Loading