跳到正文
cccskills
免费在 GitHub 公开

handsontable-e2e-testing

Maintain existing Handsontable browser tests built with Jasmine and Puppeteer. Covers asynchronous operations, simulated user input, assertions that work across themes, targeted test runs, and guidance for moving broken or unreliable tests to Playwright.

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

原文查看原文说明

Use ONLY when maintaining the FROZEN legacy Jasmine/Puppeteer E2E suite (*.spec.js) – editing an existing spec, or migrating a broken one to Playwright. NOT for new E2E: new E2E is Playwright, use the `handsontable-playwright-e2e` skill. Covers the legacy boilerplate, async/await rules, global helpers, event simulation, and theme-agnostic assertions.

集成工具Playwright
查看安装方法

适用场景

  • Updating an existing Jasmine browser test
  • Fixing assertions that fail under different themes
  • Checking simulated mouse, keyboard, and touch input
  • Planning migration of unreliable tests to Playwright

包含的文件(1)

  • SKILL.md15.2 KB

SKILL.md(原文)

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

Handsontable E2E testing (legacy Jasmine/Puppeteer, frozen)

This suite is frozen. New E2E is Playwright: use the handsontable-playwright-e2e skill and put specs in tests/e2e/. This guide covers maintaining existing *.spec.js files. The presence gate blocks a newly added *.spec.js, and appending three or more new it blocks to a modified frozen spec draws its non-blocking frozen-suite-growth advisory (state the justification in the PR if the frozen tier is right). Migrate a broken or flaky legacy spec to Playwright.

Boilerplate

describe('MyFeature', () => {
  const id = 'testContainer';

  beforeEach(function() {
    this.$container = $(`<div id="${id}"></div>`).appendTo('body');
  });

  afterEach(function() {
    if (this.$container) {
      destroy();
      this.$container.remove();
    }
  });

  it('should do something', async() => {
    handsontable({ data: createSpreadsheetData(5, 5) });
    await selectCell(0, 0);
    expect(getDataAtCell(0, 0)).toBe('A1');
  });
});

ESLint enforces: every it() callback is async, and every HOT API call (~50+ methods) is await-ed.

Global helpers

Injected automatically; full list in test/helpers/common.js.

  • Instance: handsontable(), destroy(), updateSettings(), render()
  • Data: createSpreadsheetData(), getDataAtCell(), getData(), setDataAtCell()
  • Structure: countCols(), countRows(), alter()
  • Selection: selectCell(), selectCells(), getSelected(), getSelectedRange()
  • DOM: getCell(), spec(), hot()
  • Plugins: getPlugin()
  • Theme layout: getLoadedTheme(), getThemeLayout() (see handsontable/.ai/TESTING.md)
  • Iframe doc.write theme CSS: getE2eThemeStylesheetLinkTagsHtml() (all themes), getE2eThemeStylesheetLinkTagHtml(key), getE2eNormalizeStylesheetLinkTagHtml() from common.js; the theme list is E2E_REGISTERED_THEME_KEYS in themeLayoutFromTokens.js, auto-discovered from src/themes/theme/index.ts.

Write the bare global (countCols(), await alter('remove_col', 2, 1)) instead of hot().countCols(). The mutating globals (alter(), setDataAtCell(), selectCell(), ...) auto-render and must be await-ed. Use hot() only for a method with no bare-global wrapper.

Theme-agnostic assertions

Every test passes under every theme. Never branch on getLoadedTheme(), layout.densityLevel, or a theme name in a spec, and never hardcode per-theme pixel values. Build expectations from const layout = getThemeLayout() (token-backed, from test/helpers/themeLayoutFromTokens.js: token primitives, overlayHeight / verticalScrollForRow helpers, and scenario helpers such as e2eGcrEditedCellOuterHeight, e2eManualRowResizerPositionFixedTopMasterFourthRow) or from live DOM measurements. themeLayoutFromTokens(themeName) reads density and tokens from handsontable/src/themes/theme/<name>.ts, so a density change propagates to all tests. Density triplets ({ compact: N, default: N, comfortable: N }) are not used anywhere.

For a value that is not token-derivable (text shaping, autosize widths, pixel rounding):

  • Plugin API reads: hot().getColWidth(col), hot().getRowHeight(row), hot().getPlugin('autoColumnSize').getColumnWidth(col)
  • DOM measurements: getCell(r, c).offsetWidth/offsetHeight, $el.getBoundingClientRect(), window.getComputedStyle(el).padding*
  • Relational assertions: toBeGreaterThan(previousValue), toBeLessThanOrEqual(containerWidth)
  • Tolerance: toBeAroundValue(expected, 2) or expect(Math.abs(actual - expected)).toBeLessThanOrEqual(1)

Viewport helpers (globals from common.js):

  • expectedVisibleRows(containerHeight, colHeaderRows = 1): number of fully visible data rows
  • expectedLastFullyVisibleRow(containerHeight, colHeaderRows = 1): 0-based index of the last fully visible row
  • containerHeightForRows(rowCount, colHeaderRows = 1): height that guarantees exactly rowCount fully visible rows (use it instead of hardcoded height: 200)
  • scaleHeight(mainThemeHeight) / scaleHeightWithScrollbar(mainThemeHeight): scale a main-theme pixel height to the current theme's row height
  • getPaginationContainerHeight(): live pagination bar height

Order of preference: (1) a named layout.e2e*() helper when a shared formula exists, (2) a direct formula in primitives (layout.defaultDataRowHeight + layout.cellBorderWidth), (3) a DOM/plugin-API read, (4) a relational assertion. Express theme differences through layout primitives, which already vary per theme.

Adding a new theme: the handsontable-css-dev skill has the four-layer token process. E2E-specific steps: (1) tokens at src/themes/static/variables/tokens/<name>.ts, (2) colors at src/themes/static/variables/colors/<name>.ts, (3) icons at src/themes/static/variables/icons/<name>.ts (or reuse one), (4) CSS source src/themes/static/css/theme/ht-theme-<name>.css + -no-icons.css variant, (5) theme module src/themes/theme/<name>.ts exporting { name, density, icons, colors, tokens }, (6) re-export from src/themes/theme/index.ts, (7) add new token keys to the VALID_TOKEN_KEYS allow-list in src/themes/engine/utils/validation.ts, (8) add new token keys to the TokenKey union in src/themes/types.ts, (9) add the theme to the six-leg theme literal of the puppeteer job's matrix in .github/workflows/e2e.yml (the bundle literal holds {label, dump} pairs and needs nothing), add the new theme's legs as rows of the Playwright job's include table (one row per shard), then update the expected theme list and SHARD_TABLE in .github/scripts/__tests__/e2e-legs.test.mjs and DURATIONS_LEGS in .github/scripts/lib/playwright-durations.mjs; the pins hold the Jasmine literals by value and the two matrices to the same legs, so test:tooling stays red until all of them agree — a pull request still runs the main theme alone, the new theme runs on the E2E nightly. themeLayoutFromTokens.js, common.js, and specs need no edits.

Details: handsontable/.ai/TESTING.md ("Data-Driven Theme Assertions").

Event simulation

  • Mouse: mouseDown(), mouseUp(), mouseOver(), mouseClick(), mouseDoubleClick() from test/helpers/mouseEvents.js
  • Keyboard: keyDown(), keyUp(), keyDownUp() from test/helpers/keyboardEvents.js
  • Touch (from test/helpers/common.js, await both): triggerTouchEvent(type, target) dispatches a single event ('touchstart' / 'touchend'); simulateTouch(target) runs the full Android sequence touchstart, touchend, mousedown, mouseup, click (with preventDefault handling). A double-tap is two touchstart + touchend pairs on the same cell.

Waiting in an edited spec (hard rules)

A broken or flaky spec migrates to Playwright; these rules cover an edit you must make in place.

  • Pin the viewport before a rendered-DOM count assertion. countRenderedRows(), countRenderedCols(), and any tbody tr count depend on the container size, which varies per theme and machine. Size the container with containerHeightForRows(n) or scrollViewportTo() the target into view first.
  • Use waitUntil(condition, timeout) instead of sleep() and waitForNextAnimationFrames(). It is a spec global from test/helpers/common.js: it polls every frame and rejects with a named reason when the state never arrives. waitForNextAnimationFrames() is a fixed sleep in frames (at most 2 real frames, normalizeFrameCount caps it, plus 16 ms padding per requested frame). Every sleep() call warns (handsontable/no-fixed-sleep-in-spec, warn level); the diff-scoped ratchet (.github/scripts/lint-ratchet.mjs, at pre-push and in the CI Lint / core job, since 2026-09-07) fails a NEW one on an added line.
  • Migrate a spec that needs it.flaky() instead of adding retries: new it.flaky() sites are lint-warned (handsontable/no-new-it-flaky), and the same ratchet fails one on an added line.

What to test for plugins

  • Enable via settings: handsontable({ myPlugin: true })
  • Disable via updateSettings({ myPlugin: false })
  • Programmatic: getPlugin('myPlugin').enablePlugin() / .disablePlugin()
  • Non-consecutive selections and header selections.
  • Coordinate system edge cases (physical vs visual vs renderable).

Run commands

  • All: npm run test:e2e --prefix handsontable
  • Targeted: npm run test:e2e --prefix handsontable --testPathPattern=<regex>, matched against test file paths during the Rspack .dump step (e.g. collapsibleColumns, ghostTable, textEditor, nestedHeaders/__tests__/hidingColumns)
  • Read the spec count of a targeted run. A pattern that matches nothing still ends 5 specs, 0 failures with exit 0 (measured on four no-match patterns in DEV-2911). test/e2e/index.js tests the pattern (case-insensitive) against webpack context keys, relative to handsontable/src/ or handsontable/test/e2e/: use ./validators/dropdownValidator/__tests__/dropdownValidator.spec.js, not src/validators/.... Use one plain pattern per command with no shell characters: scripts/run.mjs passes it to sh -c unquoted, so (a|b) is a syntax error and a\|b reaches the regex as a literal \| (validat covers every validator and validation spec).
  • With theme: add --theme=horizon (available: classic, main, horizon; default main).
  • Rebuild first: the runner loads dist/handsontable.js; run npm run build --prefix handsontable after changing src/**.

Parallel runs: invocations with different patterns or themes can run simultaneously. The dump step hashes testPathPattern + theme into a run ID and writes test/dist/main.entry.<runId>.js and test/E2ERunner-<runId>.html; the Puppeteer runner picks its own free port starting at 8086.

Iterating on one area: test:e2e.watch keeps the dev server running and re-bundles and re-runs on every source change:

npm run test:e2e.watch --prefix handsontable --testPathPattern=filters --theme=horizon

Split dump + puppeteer (what CI does): pass --testPathPattern AND --theme to both npm run commands. The Puppeteer script recomputes the dump's hash to find the runner HTML, and a mismatch fails with "Runner HTML not found at ...". The puppeteer job of .github/workflows/e2e.yml is the canonical example; the same applies to test:production.dump + test:e2e.puppeteer. The one-shot npm run test:e2e passes the flags to both halves itself.

A generic test/E2ERunner.html (no run ID) is regenerated alongside the per-run variant for manual browser testing. Specs that inject iframes with relative CSS paths (e.g. afterRefreshDimensions, Selection) rely on the runner living in test/, which is why the per-run HTML stays there too.

Where the suite runs in CI

A pull request and a develop push run this suite on one leg, E2E / UMD (theme: main) — plain UMD on the main theme, the shape npm run test:e2e defaults to. The six legs (three themes × UMD / the minified bundle of test:production) run on the E2E nightly (weeknights on develop), on the rc/stable publish path and on a master push (.ai/CI.md, the Jasmine single-leg entry). Two consequences:

  • A spec red on another theme or on the minified bundle alone first shows on the nightly, with branch: develop and the runner's isolation verdict in the flake ledger at https://handsontable.github.io/handsontable/test-health/. passes alone (order-dependent) is a migration ticket like any other recurring red; fails alone is fixed or migrated before the next RC cut, because the rc/stable path runs the six legs and its publish needs them green. Never add a leg back to pull requests for either.
  • A change you suspect is theme-sensitive is checked before the push: --theme=horizon / --theme=classic on the targeted run, and npm run test:production --prefix handsontable --testPathPattern=<regex> for the minified bundle.

Debugging (capturing values from the browser)

Specs run in a headless browser. The Puppeteer runner (test/scripts/run-puppeteer.mjs) forwards only console messages whose text starts with DEBUG, printed as [BROWSER] <text>:

console.log(`DEBUG state ${JSON.stringify({ labels: getColHeaders(), count: countCols() })}`);

Filter the run output: npm run test:e2e --prefix handsontable --testPathPattern=<regex> 2>&1 | grep DEBUG.

  • JSON.stringify omits keys whose value is undefined; use String(value) to distinguish undefined/false/null.
  • expect(actual).toEqual('SENTINEL') also works: assertion diffs always reach the terminal.

Test location

Specs live under src/ next to the code they test. The spec filename matches the method, hook, or setting name exactly (getSourceData.spec.js, afterChange.spec.js, height.spec.js).

What is testedDirectory
Core method (e.g., getSourceData)src/__tests__/core/<methodName>.spec.js
Hook (e.g., afterChange)src/__tests__/hooks/<hookName>.spec.js
Setting (e.g., height)src/__tests__/settings/<settingName>.spec.js
Pluginsrc/plugins/{name}/__tests__/*.spec.js
Keyboard shortcutssrc/shortcuts/__tests__/keyboardShortcuts/<name>.spec.js
i18nsrc/i18n/__tests__/<name>.spec.js
Mobile-specificsrc/__tests__/mobile/<name>.spec.js

Do not add spec files to test/e2e/; it is no longer their home.

Reference organization: src/plugins/pagination/__tests__/ (separate dirs for options, methods, hooks, strategies).

Common mistakes

  • Using the hot(). form instead of the bare global.
  • Importing helpers manually (they are globals).
  • Skipping the updateSettings() cycle.
  • Missing edge cases: large datasets, coordinate boundaries, enable/disable cycles.
  • Testing only one keyboard navigation mode: cover both spreadsheet and data grid.
  • Trusting the spec count. Before the bridge reporter sanitized failed expectations (test/helpers/jasmine-bridge-reporter.js, shared with the Walkontable runner), a failing spec with a cyclic expected or actual (toBe(window), toEqual([overlay, ...])) was dropped: Running N specs. in --verbose mode, N-1 specs, 0 failures at the end, exit 0. The bridge now reports it as a failure ([unserializable Window]); if a count comes up short, compare the Running N specs. line against the summary with npm run test:e2e -- --testPathPattern=<file> --verbose.
  • Throwing inside a describe body. Jasmine turns it into a suite-level failure the runner does not report, and every later spec in that block never registers. it.skip() does exactly this (Jasmine has no it.skip) and hid five specs of nestedHeaders/__tests__/rowspan.spec.js. After editing a spec file, the spec count of a --testPathPattern run must equal the file's it( calls plus 5, because every run carries the 5 MemoryLeakTest specs; a count equal to the it( calls is 5 short.

Full testing docs: handsontable/.ai/TESTING.md. Key files: test/helpers/common.js, test/helpers/mouseEvents.js, test/helpers/keyboardEvents.js.

评价

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

评论

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

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

提交后会立即公开。请勿填写邮箱、电话等个人信息。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 的全部技能

报告此技能的问题