Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ module.exports = {
},
{
files: ['./**/*.ts', './**/*.tsx', './**/*.js', './**/*.jsx', './**/*.vue'],
plugins: ['html', 'cypress', '@typescript-eslint', 'simple-import-sort'],
env: {
'cypress/globals': true,
},
plugins: ['html', '@typescript-eslint', 'simple-import-sort'],
globals: {
document: false,
window: false,
Expand Down
56 changes: 37 additions & 19 deletions .github/instructions/tiptap.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ Key points for AI assistants:
│ ├─ extension-*/ # Individual extensions
│ ├─ pm/ # ProseMirror related internals and helpers
│ └─ ... # Shared utilities, framework bindings, etc.
├─ demos/ # Vite app for live examples
│ ├─ react/ # React demos
│ └─ vue/ # Vue demos
├─ tests/ # Cypress e2e tests that run against the demos
├─ demos/ # Vite app for live examples and colocated e2e specs
│ ├─ src/
│ │ ├─ react/ # React demos
│ │ └─ vue/ # Vue demos
│ └─ test/ # Playwright helpers (getEditor, setEditorContent, ...)
├─ .changeset/ # Changesets for versioning and changelogs
└─ .github/ # Workflows and docs like this file
```
Expand All @@ -41,7 +42,7 @@ Notes:

* All packages we publish or use live under `packages/*`.
* The `demos/` folder contains a Vite app. It automatically discovers and parses React and Vue demos so they appear in the UI without manual wiring.
* Cypress tests in `tests/` expect the demos to be available on `http://localhost:3000`.
* Playwright e2e specs live alongside their demos as `demos/src/**/index.spec.ts`. `playwright.config.ts` auto-starts the Vite dev server on `http://127.0.0.1:4080` — no need to launch it manually.

## NPM scripts

Expand All @@ -51,8 +52,14 @@ Scripts defined at the repo root:
* `pnpm build` - build all packages via Turborepo
* `pnpm lint` - run eslint checks
* `pnpm lint:fix` - run prettier + eslint fix
* `pnpm test:open` - open Cypress against `tests/`
* `pnpm test:run` - run Cypress in headless mode
* `pnpm test:e2e` - run Playwright e2e tests headlessly in Chromium
* `pnpm test:e2e:firefox` - same, in Firefox
* `pnpm test:e2e:all` - same, in both browsers
* `pnpm test:e2e:open` - run Playwright in UI mode (Chromium tests)
* `pnpm test:e2e:open:firefox` - UI mode, Firefox tests
* `pnpm test:e2e:open:all` - UI mode, both browsers selectable
* `pnpm test:e2e:report` - open the HTML report from the last run
* `pnpm test:unit` - run Vitest unit tests in `packages/**/__tests__/`
* `pnpm test` - build then run all tests
* `pnpm serve` - build and serve the demos on port 3000
* `pnpm publish` - build and publish with Changesets
Expand Down Expand Up @@ -94,23 +101,33 @@ When adding a demo, keep it small and self-contained, with imports from publishe

---

## Testing with Cypress
## Testing

* Cypress lives in `tests/` and drives the demos in a browser.
* Tests assume the app is running on `http://localhost:3000`.
Two layers:

Workflow:
* **Unit tests** with Vitest in `packages/**/__tests__/` (happy-dom). These test `@tiptap/core` and individual extensions in isolation.
* **E2E tests** with Playwright, colocated next to their demos as `demos/src/**/index.spec.ts`. They drive the real Vite-served demo pages in Chromium.

Run them:

```bash
pnpm dev # terminal A
pnpm test:open # terminal B
pnpm test:unit # Vitest
pnpm test:e2e # Playwright headless (Chromium)
pnpm test:e2e:firefox # Playwright headless (Firefox)
pnpm test:e2e:all # both browsers — every test twice
pnpm test:e2e:open # UI mode (Chromium tests)
pnpm test:e2e:open:firefox # UI mode (Firefox tests)
pnpm test:e2e:open:all # UI mode, switch between browsers in the project picker
pnpm test:e2e:report # open the HTML report from the last run
```

or for headless CI runs:
Playwright auto-starts the demo dev server (`pnpm -C demos run start:e2e` on port 4080) via `playwright.config.ts` — no separate terminal needed. Shared helpers live in `demos/test/helpers.ts`: `getEditor`, `setEditorContent`, `clickButton`. Use `demos/src/Commands/Cut/index.spec.ts` as a canonical template when adding new specs.

```bash
pnpm test:run
```
Browser setup:

* CI installs Chromium only (cached between runs) and only runs the Chromium project.
* For local Firefox testing, install it once with `pnpm exec playwright install firefox` (~80MB).
* UI mode (`--ui`) always opens its host window in Chromium — that's the Playwright UI app itself, not the browser running your tests. Tests still execute in the project you selected (check the trace metadata or `browserName` fixture if you need to confirm).

---

Expand Down Expand Up @@ -198,7 +215,8 @@ Run the following to validate changes quickly:
```bash
pnpm lint
pnpm build
pnpm test # runs unit and/or cypress where configured
pnpm test:unit # Vitest
pnpm test:e2e # Playwright (auto-starts the demo server)
pnpm dev # optionally run the demos and open http://localhost:3000
```

Expand All @@ -222,7 +240,7 @@ If a single package is failing types, run a targeted build for that package (e.g
### Troubleshooting notes

- If CI fails with dependency or lockfile errors, run `pnpm reset` locally and re-run the build.
- For flaky Cypress tests, run the demo locally with `pnpm dev` and reproduce the failing test in `pnpm test:open`.
- For flaky Playwright tests, reproduce locally with `pnpm test:e2e:open` (UI mode) or rerun with `--trace on` and inspect via `pnpm test:e2e:report`.

---

Expand Down
118 changes: 78 additions & 40 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,16 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile --strict-peer-dependencies

- name: Install Cypress binary
run: pnpm exec cypress install

- name: Pack dependency artifacts
run: |
tar -czf /tmp/pnpm-store.tar.gz ${{ env.PNPM_STORE_DIR }}
tar -czf /tmp/cypress-cache.tar.gz -C "$HOME/.cache" Cypress

- name: Upload dependency artifacts
uses: actions/upload-artifact@v7
with:
name: node-dependencies
path: |
/tmp/pnpm-store.tar.gz
/tmp/cypress-cache.tar.gz
retention-days: 1

check-linting-formatting:
Expand Down Expand Up @@ -194,24 +189,15 @@ jobs:
run: pnpm run test:unit

run-e2e-tests:
name: Run e2e tests
name: Run e2e tests (shard ${{ matrix.shard }}/${{ matrix.total }})
runs-on: ubuntu-latest
needs: build-packages
timeout-minutes: 45

timeout-minutes: 30
strategy:
fail-fast: false
matrix:
test-spec:
- { id: integration, name: Integration, spec: './tests/cypress/integration/**/*.spec.{js,ts}' }
- { id: demos-commands, name: 'Demos/Commands', spec: './demos/src/Commands/**/*.spec.{js,ts}' }
- { id: demos-examples, name: 'Demos/Examples', spec: './demos/src/Examples/**/*.spec.{js,ts}' }
- { id: demos-experiments, name: 'Demos/Experiments', spec: './demos/src/Experiments/**/*.spec.{js,ts}' }
- { id: demos-extensions, name: 'Demos/Extensions', spec: './demos/src/Extensions/**/*.spec.{js,ts}' }
- { id: demos-guidecontent, name: 'Demos/GuideContent', spec: './demos/src/GuideContent/**/*.spec.{js,ts}' }
- { id: demos-guidegettingstarted, name: 'Demos/GuideGettingStarted', spec: './demos/src/GuideGettingStarted/**/*.spec.{js,ts}' }
- { id: demos-marks, name: 'Demos/Marks', spec: './demos/src/Marks/**/*.spec.{js,ts}' }
- { id: demos-nodes, name: 'Demos/Nodes', spec: './demos/src/Nodes/**/*.spec.{js,ts}' }
shard: [1, 2, 3, 4]
total: [4]

steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -241,9 +227,6 @@ jobs:
- name: Restore dependencies
run: pnpm install --offline --frozen-lockfile --strict-peer-dependencies

- name: Restore Cypress cache
run: mkdir -p "$HOME/.cache" && tar -xzf /tmp/node-dependencies/cypress-cache.tar.gz -C "$HOME/.cache"

- name: Download build artifacts
uses: actions/download-artifact@v8
with:
Expand All @@ -253,29 +236,84 @@ jobs:
- name: Restore build output
run: tar -xzf /tmp/build-output/build-output.tar.gz

- name: Test ${{ matrix.test-spec.name }}
uses: cypress-io/github-action@v7.3.0
- name: Get Playwright version
id: playwright-version
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT

- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
install: false
start: pnpm exec http-server ./demos/dist -s -p 3000
wait-on: http://localhost:3000
spec: ${{ matrix.test-spec.spec }}
project: ./tests
browser: chrome
quiet: true

- name: Export screenshots (on failure only)
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}

- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium

- name: Install Playwright system deps
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium

- name: Run Playwright tests
run: pnpm exec playwright test --project=chromium --shard=${{ matrix.shard }}/${{ matrix.total }}

- name: Upload blob report
if: always()
uses: actions/upload-artifact@v7
if: failure()
with:
name: cypress-screenshots-${{ matrix.test-spec.id }}
path: tests/cypress/screenshots
name: e2e-blob-report-${{ matrix.shard }}
path: blob-report
retention-days: 7

- name: Export screen recordings (on failure only)
merge-e2e-reports:
name: Merge e2e reports
runs-on: ubuntu-latest
needs: run-e2e-tests
if: always()
timeout-minutes: 10

steps:
- uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@v5
with:
version: ${{ env.PNPM_VERSION }}

- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}

- name: Configure pnpm store
run: pnpm config set store-dir ${{ github.workspace }}/${{ env.PNPM_STORE_DIR }}

- name: Download dependency artifacts
uses: actions/download-artifact@v8
with:
name: node-dependencies
path: /tmp/node-dependencies

- name: Restore pnpm store
run: tar -xzf /tmp/node-dependencies/pnpm-store.tar.gz

- name: Restore dependencies
run: pnpm install --offline --frozen-lockfile --strict-peer-dependencies

- name: Download blob reports
uses: actions/download-artifact@v8
with:
path: all-blob-reports
pattern: e2e-blob-report-*
merge-multiple: true

- name: Merge into HTML report
run: pnpm exec playwright merge-reports --reporter html ./all-blob-reports

- name: Upload merged HTML report
uses: actions/upload-artifact@v7
if: failure()
with:
name: cypress-videos-${{ matrix.test-spec.id }}
path: tests/cypress/videos
retention-days: 7
name: playwright-html-report
path: playwright-report
retention-days: 14
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ yarn-error.log*
.rts2_cache_es
.rts2_cache_umd

tests/cypress/videos
/tests/cypress/screenshots
# Playwright
/playwright-report
/test-results
/playwright/.cache
/blob-report
# Ignore intellij project files
.idea

Expand Down
14 changes: 14 additions & 0 deletions .lintstagedrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = files => {
const filteredFiles = files.filter(file => /\.(ts|tsx|js|jsx|vue)$/.test(file) && !file.includes('/tests_backup/'))

if (filteredFiles.length === 0) {
return []
}

const fileList = filteredFiles.join(' ')

return [
`prettier --write ${fileList}`,
`eslint --fix --quiet --no-error-on-unmatched-pattern ${fileList}`,
]
}
Loading
Loading