跳到正文
cccskills
免费在 GitHub 公开

performance-testing

Add or update Handsontable performance tests for rendering and user interactions. The skill guides trace recording, measurement boundaries, baseline comparisons, and reports that help reviewers identify performance changes in pull requests.

中文简介正在准备中。当前显示英语简介。

原文查看原文说明

Use when adding, modifying, or debugging Handsontable performance test scenarios in performance-tests/ - covers the CDP trace-based measurement system, scenario structure (fixture + config + spec), trace-parser integration, hook timing for filtering/sorting, golden snapshot workflow on GitHub Pages, and the CI comparison pipeline. Trigger whenever work touches performance-tests/ files, when asked to benchmark a Handsontable feature, or when adding a new performance scenario.

集成工具GitHubPlaywright
查看安装方法

适用场景

  • Add a performance test for a Handsontable feature
  • Investigate filtering or sorting performance
  • Compare performance changes in a pull request
  • Fix inconsistent measurement windows or baselines

包含的文件(1)

  • SKILL.md13.7 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Performance Testing

performance-tests/ measures rendering and interaction performance with Playwright + CDP traces. It is a standalone package outside the pnpm workspace (own package.json, node_modules, ESLint config). Each scenario traces one interaction, parses the trace into DevTools categories (scripting, rendering, painting, idle), and produces a markdown PR comment plus an HTML report compared against a golden baseline from develop. Fuller reference: performance-tests/README.md.

Package structure

scripts/run.mjs (build HOT UMD, copy to fixtures/, run Playwright), scripts/replay-goldens.mjs (replays gh-pages develop goldens to re-derive callout thresholds), trace-parser.mjs, lib/ (trace-runner.mjs owns HARNESS_VERSION; environment.mjs, snapshot-store.mjs, median-snapshot.mjs, thresholds.mjs, hook-timing.mjs, scroll-utils.mjs, fs-utils.mjs, teardown.mjs, report builders), scenarios/<name>/{scenario.config.mjs, fixture.html, <name>.spec.ts}. Playwright runs sequentially, 1 worker, 5 min timeout, chromium only. fixtures/, golden/ (fetched from gh-pages) and output/ are gitignored.

Adding a scenario

Three files in scenarios/<name>/.

1. scenario.config.mjs

// Grid: <rows> x <cols> -- <rationale for this grid size>
export default {
  name: 'my-scenario',   // must equal the directory name
  warmupRuns: 1,
  iterations: 3,
  measurementVersion: 1,
};

name determines output/<name>/, and teardown reads measurementVersion by that name. Comment the grid size and why.

Bump measurementVersion when the spec changes what the marked window contains or when iterations changes: the median baseline draws only on develop goldens at the same version. HARNESS_VERSION covers runner-wide changes; when both change in one PR the harness bump already restarts the golden pool, so skip the scenario bump and say so in the config comment.

2. fixture.html

Standalone page that exposes the instance as window.__hot:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>My Scenario</title>
  <link rel="stylesheet" href="../../fixtures/handsontable.css">
  <script src="../../fixtures/handsontable.full.js"></script>
</head>
<body>
<div id="hot"></div>
<script>
  window.__hot = new Handsontable(document.getElementById('hot'), {
    data: Handsontable.helper.createSpreadsheetData(5000, 10),
    rowHeaders: true, colHeaders: true, width: 1280, height: 600,
    autoRowSize: false, autoColumnSize: false,
    licenseKey: 'non-commercial-and-evaluation',
  });
</script>
</body>
</html>

Set autoRowSize: false and autoColumnSize: false (async sizing interferes with measurements). CSS is handsontable.css (no full CSS variant); JS is handsontable.full.js.

3. Spec

import { test } from '@playwright/test';
import path from 'node:path';
import { runTracedScenario } from '../../lib/trace-runner.mjs';
import config from './scenario.config.mjs';

const fixturePath = path.resolve(import.meta.dirname, 'fixture.html');

test(config.name, async({ page }) => {
  await page.goto(`file://${fixturePath}`);
  await page.waitForFunction(() => (window as any).__hot, undefined, { polling: 100 });
  await runTracedScenario({
    page,
    warmupRuns: config.warmupRuns,
    iterations: config.iterations,
    outputDir: path.resolve('output', config.name),
    actionFn: async() => { /* measured action */ },
    // optional: setupFn, resetFn, afterActionFn, skipSettle
  });
});

runTracedScenario creates the output dir, runs untraced warmups, runs traced iterations, and writes iteration-{n}.json. Add a resetFn for repeatable actions, otherwise iterations 2+ start from iteration 1's end state.

Hook timing (filtering/sorting)

import { injectHookTimer, getHookTiming, saveHookTimings } from '../../lib/hook-timing.mjs';

await injectHookTimer(page, 'beforeFilter', 'afterFilter');   // before tracing; call again in resetFn (idempotent, resets the store)
const hookDeltas: number[] = [];

// in runTracedScenario:
afterActionFn: async() => {   // afterActionFn, because getHookTiming is a page.evaluate and inside actionFn its CDP round trip is measured as part of the operation
  const timing = await getHookTiming(page, 'beforeFilter', 'afterFilter');
  if (timing.deltaMs != null) hookDeltas.push(timing.deltaMs);
},

await saveHookTimings(path.resolve('output', config.name), hookDeltas); // writes hook-timing.json; teardown picks it up

Scroll helpers

scrollToRow(page, row) and scrollToColumn(page, col) from lib/scroll-utils.mjs combine scrollViewportTo() with a deterministic waitForFunction on the index mapper; use them in place of waitForTimeout.

Existing scenarios

Listed under scenarios/: scroll-down/up/right/left (500 wheel events each; up and left pre-scroll via scrollToRow/scrollToColumn), filtering, sorting, cell-editing (5000x10), initial-load, source-data-validator-load (initial-load's fixture plus the one option), undo-edit, undo-sort, undo-remove-rows (redo in resetFn), row-selection-select-all, row-selection-deselect-all, checkbox-header-toggle, checkbox-column-set-data (the PRO-87 row selection and "check all" paths). Filtering, sorting and the undo scenarios use hook timing. Grids are 10000x50 (vertical scroll), 10x5000 (horizontal scroll), 100000x100 (the rest).

Iterations: 3 for scroll and cell-editing; 5 for filtering, sorting, initial-load, source-data-validator-load, undo-edit, undo-sort, undo-remove-rows, row-selection-select-all, row-selection-deselect-all, checkbox-header-toggle, checkbox-column-set-data. Each of those eleven states its reason in its scenario.config.mjs (short windows on a 300 to 350 MB heap where one GC pause moves a mean of three by 10 to 20%; for source-data-validator-load, a 20% run-to-run spread after removing the runner factor). lib/__tests__/scenario-configs.test.mjs pins the counts, so change config and test together. Keep the scroll scenarios at 3: each iteration is 500 wheel round trips.

Run commands

cd performance-tests && node scripts/run.mjs        # build HOT + copy fixtures + run all
npx playwright test --grep "scroll-down"            # one scenario (fixtures must exist)
PERF_MODE=golden node scripts/run.mjs               # save baseline snapshot
PERF_MODE=compare node scripts/run.mjs              # load golden, generate delta report
npm run lint
npm run typecheck                                   # spec files

handsontable/dist/ and handsontable/styles/ must exist before running npx playwright test directly; scripts/run.mjs builds them.

CI workflow

.github/workflows/performance-tests.yml:

  • push to develop: runs all scenarios in golden mode, deploys snapshots.json + report.html to gh-pages under performance-reports/develop/<timestamp>/, updates latest.json (the pointer for PR comparisons), and builds a history index page. The run is also compared against the trailing median of compatible develop goldens, for its report, job summary and one ::warning annotation per regressed scenario. The snapshot it saves is never derived from history (teardown saves before it loads).
  • pull_request: fetches the last 20 develop goldens into golden/history/ (plus latest.json as a single-file fallback), runs all scenarios in compare mode, posts a sticky PR comment (summary table + regression callouts), and deploys the HTML report to performance-reports/<branch-slug>/.

Push retries with rebase (up to 3 attempts) for concurrent gh-pages writes.

Baseline compatibility and provenance

All in lib/environment.mjs. A Playwright bump (new Chromium) once shifted initial-load by -18% and sorting by -20% in one develop push, and the median-of-5 baseline carried the old browser's numbers for five more pushes.

  • Provenance. lib/setup.mjs (Playwright globalSetup) records the Chromium build (browser.version()), CPU model and count, memory, platform and GitHub runner image into output/environment.json. Teardown stamps it on the snapshot (environment, harnessVersion); both reports print it in the footer.
  • Compatibility key. { chromium, platform, harnessVersion } per snapshot, measurementVersion per scenario. computeMedianSnapshot(..., { compatibleWith }) draws only on goldens with the same key, and within a scenario only on entries at the same measurementVersion. A golden without the provenance fields is excluded, as a pre-marks golden is by windowSource. With no compatible golden the comment says "no comparable baseline" and why (describeKeyMismatch, or one of BASELINE_REFUSALS in snapshot-store.mjs: empty history, no marks-valid golden, every scenario redefined, disjoint scenarios, empty latest.json). With exactly one, the single-file fallback serves and the footer says "single develop run"; the median needs two. Bump HARNESS_VERSION in trace-runner.mjs when the runner changes what a window contains (settle, GC between iterations, work moved in or out); bump a scenario's measurementVersion when only that spec's definition changes.
  • Run shift. The comment's Δ vs shift column and the footer's Run shift line give the median delta across scenarios (how much faster or slower this runner ran than the baseline's; the per-run factor spans 0.63x-1.12x across develop goldens, and removing it takes the scroll scenarios' CV from ~13% to ~3%). It is reported only; callouts fire on the raw Δ Total, because a change that slows every scenario alike is invisible to a median.

scripts/replay-goldens.mjs groups goldens by key before replaying; its Compatibility groups block shows the counts per group.

The trace pipeline

The measured window (read before adding a scenario)

Everything published describes the slice between the two performance.marks that runTracedScenario() writes around the action.

  • Keep harness round trips out of actionFn: a page.evaluate that reads a value back is measured as part of the operation. Put readbacks in afterActionFn, which runs after the end mark.
  • A resetFn must leave no frame behind. The runner settles after setupFn and resetFn, and skipSettle does not turn those off. scrollToRow/scrollToColumn report trimming, not scroll position, so their waitForFunction returns before the scroll has rendered.

A category measured as exactly 0, or a CV of sqrt(n) × 100% (one nonzero iteration among zeros: 173.21% at three iterations, 223.61% at five, using the sample standard deviation calcCv uses), means the window is wrong, not that the operation was cheap.

  1. Spec calls runTracedScenario(): forced GC (over a control CDP session), Tracing.start, start mark, action, settle, end mark, afterActionFn, forced GC + Runtime.getHeapUsage readback, Tracing.end -> raw JSON per iteration, plus heap-after-gc.json per scenario. The GC before tracing keeps the previous reset's garbage out of the window (initial-load's third iteration read ~50% slower before it); the readback is recorded as updateCounters.jsHeapAfterGcBytes. Both are part of HARNESS_VERSION 2.
  2. Teardown (lib/teardown.mjs) runs parseTrace() (trace-parser.mjs) on output/*/iteration-*.json, measuring the marked window (the auto-zoomed window only for traces recorded without marks), averages via averageParsedTraces(), collects per-iteration values for CV%, and strips _iterationValues and _debug from saved snapshots.
  3. PERF_MODE=golden: snapshot-store.mjs saves averaged results for gh-pages. PERF_MODE=compare: teardown loads the golden and the report shows deltas.

Shared utilities

Import from lib/ (fs-utils.mjs exists, scroll-utils.mjs, hook-timing.mjs, environment.mjs, thresholds.mjs) in specs and scripts. thresholds.mjs is the single source of the callout thresholds and colour bands: reference them from there, never restate either number elsewhere, and derive any retune from scripts/replay-goldens.mjs, never by eye. Heap has per-scenario overrides in HEAP_THRESHOLDS_BY_SCENARIO (the horizontal-scroll scenarios' peak heap is GC timing, CV 4.4-6.3%); read them through heapThresholdFor(name). Never read REGRESSION_CALLOUT_THRESHOLD_HEAP directly at a render site: a bypass disagrees with the callouts.

.mjs convention

Follow node-scripts-dev. .mjs files are plain JavaScript: use /** @type {any} */ (window) JSDoc casts instead of as any.

Common mistakes

MistakeFix
npx playwright test without built fixturesRun node scripts/run.mjs or build HOT and copy dist files
waitForFunction() without { polling }The rAF default is starved on a loaded machine. Pass undefined, { polling: 100 }; the tier's eslint config bans the default
waitForTimeout() for scroll/render waitsscrollToRow() / scrollToColumn(), or waitForFunction with a renderable-index check
Changing a window's contents without a version bumpBump HARNESS_VERSION (lib/trace-runner.mjs) for a runner change, or the scenario's measurementVersion for a spec change; otherwise the median averages two definitions of the scenario for five develop pushes
Gating a callout on Δ vs shiftGate on the raw delta; the shift is a median across scenarios and is shown beside it for the reader to weigh

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Develop and modify the Handsontable Angular wrapper with guidance on component lifecycle, settings updates, and NgZone, which controls change detection. It also covers standalone examples, global configuration, and the build and test workflow.

中文简介正在准备中。当前显示英语简介。

  • Fix settings updates in the Angular wrapper
  • Review change detection around grid operations
  • Update examples to standalone components
handsontable/handsontable2.2万2026年10月19日 更新

Open local or public web pages in BrowserStack Live for checks on real mobile devices or desktop browsers. The skill prepares temporary access to a local server, launches the requested session, and shares its dashboard link.

中文简介正在准备中。当前显示英语简介。

  • Check a local page on an Android device
  • Open a page in Safari on an iPhone or iPad
  • Inspect behavior in a specific desktop browser
handsontable/handsontable2.2万2026年10月19日 更新

Prepare user-facing changelog entries for source code changes. The skill determines whether an entry is needed, classifies the change, writes a clear title, and uses the project CLI to create a JSON file tied to the pull request.

中文简介正在准备中。当前显示英语简介。

  • Add a changelog entry for a bug fix
  • Classify a new feature or behavior change
  • Explain a breaking change and its replacement
handsontable/handsontable2.2万2026年10月19日 更新

Explore relationships between functions and files using a prebuilt code graph, a map of code dependencies. Trace bugs, inspect callers and execution paths, and assess the impact of refactoring or proposed changes.

中文简介正在准备中。当前显示英语简介。

  • Explore callers and dependencies in unfamiliar code
  • Trace a bug across multiple files
  • Assess the impact of renaming or removing code
handsontable/handsontable2.2万2026年10月19日 更新

Work with Handsontable row and column positions by distinguishing source data, displayed order, and rendered cells. It guides index conversion and helps investigate misplaced data when sorting, filtering, hiding, or moving rows and columns.

中文简介正在准备中。当前显示英语简介。

  • Investigating misplaced rows or columns
  • Converting between source and displayed indexes
  • Combining filters with manual column movement
handsontable/handsontable2.2万2026年10月19日 更新

Create Handsontable documentation examples for JavaScript, TypeScript, React, Angular, and Vue. The skill explains source files, generated JavaScript, required imports and license settings, embedding examples in guides, and checking that each framework version displays correctly.

中文简介正在准备中。当前显示英语简介。

  • Adding a code example to a feature guide
  • Preparing examples for multiple frameworks
  • Generating JavaScript examples from TypeScript
handsontable/handsontable2.2万2026年10月19日 更新

查看 handsontable 的全部技能

报告此技能的问题