FreeReportv0.10.1한국어

Testing

Test FreeReport pages in Vitest, Jest, Playwright or Cypress: read report text and rows, fill parameters, page, search and drive the designer.

@freereportjs/testing drives the viewer and the designer the way a user does — typing parameters, pressing 조회, paging, searching, selecting and editing elements — and reads what is on screen by the element ids of your report. Tests do not depend on the inner DOM, so they keep working across FreeReport versions. It works in jsdom and happy-dom (Vitest, Jest) and in a real browser; for Playwright and Cypress it gives CSS selectors.

npm i -D @freereportjs/testing

Vitest or Jest (jsdom)

jsdom has no layout, so pages never scroll into view and nothing is drawn. Call mockLayout() once: it gives elements a size, treats every page as visible and adds the browser functions the designer needs. It returns a function that undoes it.

// npm i -D @freereportjs/testing vitest jsdom   (vitest.config: test.environment = 'jsdom')
import { beforeAll, expect, it } from 'vitest';
import { createViewer } from '@freereportjs/core';
import { mockLayout, viewerHarness } from '@freereportjs/testing';

// jsdom has no layout: without this nothing scrolls into view and no page is drawn
beforeAll(() => { mockLayout(); });

it('filters by region', async () => {
  const host = document.createElement('div');
  document.body.append(host);
  createViewer(host, { definition, fetchData: p => api.sales(p) });
  const report = viewerHarness(host);
  await report.waitForReady();

  report.setParam('region', '부산');
  report.setParam('from', new Date(2026, 9, 1));
  await report.submitParams();                     // presses 조회 and waits for the pages

  expect(report.text('title')).toBe('부산 매출 보고서');   // element id "title"
  expect(report.rows(['item', 'amount'])[0]).toEqual(['노트북', '1,200,000']);
  expect(report.overflows()).toEqual([]);         // no text cut off
});
FunctionReturnsDescription
viewerHarness(el)ViewerHarnessHelpers for the viewer in el (the element given to createViewer, a wrapper host, or the viewer root).
waitForReady(timeout?)Promise<void>Wait until the current run is done; rejects with the viewer message when it failed.
state()'busy' | 'ready' | 'error'What the viewer is doing.
pageCount() / currentPage() / pageText(n)number / number / stringPages, the page in the page box, and all text of a drawn page.
text(id, row?) / texts(id) / hits(id)string / string[] / TextHit[]Text of a report element by its id: the first, the one of a detail row, all of them, or with row and page.
rows(ids) / column(id)string[][] / string[]Detail rows as a table (one cell per element id), or one element down the rows.
overflows()TextHit[]Text cut off because it does not fit its box.
setParam(name, value) / paramValues() / submitParams()void / object / Promise<void>Type or pick a parameter like a user, read the panel, press 조회 and wait for the run.
click(act) / press(key, init?) / goTo(n) / search(text)void / void / void / stringToolbar and Save menu buttons by action (next, last, pdf, xlsx…), keys on the pages, the page box, and find (returns "1 / 3").

Designer

Properties are found by their label in the property panel, so use the labels of the language you create the designer with.

import { createDesigner } from '@freereportjs/core';
import { designerHarness, mockLayout } from '@freereportjs/testing';

mockLayout();
const designer = createDesigner(host, { definition, data: { rows } });
const d = designerHarness(host);

d.select('title');                 // element id
d.setProp('글자 크기', 16);         // property by its label (lang: 'en' → 'Font size')
d.setProp('굵게', true);
d.editText('title', '월간 매출');   // double-click, type, Enter
d.select('total', { shift: true });
d.align('right');
d.press('ArrowDown', { shiftKey: true });  // 5 mm down
d.press('z', { ctrlKey: true });           // undo
d.setMode('preview');
await d.preview().waitForReady();
expect(d.preview().text('title')).toBe('월간 매출');
FunctionReturnsDescription
designerHarness(el)DesignerHarnessHelpers for the designer in el.
elementIds(band?) / elementText(id) / selected()string[] / string / string[]Elements on the canvas, the text one shows, and the selection.
select(id, { shift }) / selectBand(key) / clearSelection()voidClick an element (shift adds to the selection), a band title, or empty space.
prop(label) / setProp(label, value) / propLabels()string / void / string[]Read or change a property by its label in the panel (X, 너비, 굵게, 테두리 …), like a user.
editText(id, text, finish?)voidDouble-click a text or field, type, then Enter (or Escape).
drag(id, dx, dy) / press(key, init?)voidDrag the selection by dx, dy mm; keys on the canvas (Delete, arrows, Ctrl+Z …).
align(to) / click(act)voidAlign the selection (left, center, right, top, middle, bottom, sameWidth, sameHeight) or press a toolbar button (undo, copy, paste…).
setMode(mode) / toggleBand(key, on) / json() / applyJson(text) / preview()…Tabs, the band list, the JSON view (applyJson returns the error message) and the preview viewer as a ViewerHarness.

Playwright

selectors.viewer and selectors.designer are plain CSS selectors. Wait for selectors.viewer.ready after loading and after every 조회: the viewer is busy until the pages are drawn.

import { expect, test } from '@playwright/test';
import { selectors } from '@freereportjs/testing';
const v = selectors.viewer;

test('sales report', async ({ page }) => {
  await page.goto('/reports/sales');
  await expect(page.locator(v.ready)).toBeVisible();          // the first run is done

  await page.locator(v.paramsToggle).click();
  await page.locator(v.param('region')).selectOption('부산');
  await page.locator(v.paramsSubmit).click();
  await expect(page.locator(v.ready)).toBeVisible();          // busy until the pages are drawn

  await expect(page.locator(v.text('title'))).toHaveText('부산 매출 보고서');
  await expect(page.locator(v.text('amount', 0))).toHaveText('1,200,000');  // first detail row
  await expect(page.locator(v.overflow)).toHaveCount(0);      // no text cut off

  await page.locator(v.button('last')).click();
  const [pdf] = await Promise.all([page.waitForEvent('download'), page.locator(v.button('pdf')).click()]);
  expect(pdf.suggestedFilename()).toMatch(/\.pdf$/);
});

Cypress

import { selectors } from '@freereportjs/testing';
const v = selectors.viewer;

it('sales report', () => {
  cy.visit('/reports/sales');
  cy.get(v.ready).should('exist');
  cy.get(v.paramsToggle).click();
  cy.get(v.param('region')).select('부산');
  cy.get(v.paramsSubmit).click();
  cy.get(v.ready).should('exist');
  cy.get(v.text('title')).should('have.text', '부산 매출 보고서');
  cy.get(v.overflow).should('not.exist');
});

Attributes on the page

The helpers use these attributes; your own scripts can use them too.

SelectorValueDescription
.fr-viewer[data-fr-state]busy | ready | errorThe viewer root: busy from 조회 until the pages are drawn. data-fr-run counts finished runs.
.fr-page[data-page]1, 2, …Page boxes, 1-based. A page is drawn when it scrolls into view.
[data-fr-id]element idEverything drawn for a report element; data-fr-kind is text, box, line, image or path.
[data-fr-row]0, 1, …Detail row of the drawing (after sort and filter).
[data-fr-overflow]1Text cut off at its box.
.fr-params [name]parameter nameInputs of the parameter panel (also #fr-p-name).
[data-act]first, next, pdf, xlsx …Toolbar and menu buttons of the viewer and the designer.
.fr-dz-el[data-id]element idElements on the designer canvas.