test(chrome-ext): e2e test for the Slate editor (#1281)

This commit is contained in:
Elijah Potter 2025-05-19 09:23:51 -06:00 committed by GitHub
parent b39bdacb09
commit 0ee01d634f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 201 additions and 54 deletions

View file

@ -9,6 +9,7 @@
"ignoreUnknown": true,
"include": ["packages/**/*", "**/*.json"],
"ignore": [
"test-results",
"node_modules",
"dist",
"target",

View file

@ -13,12 +13,11 @@ build-harperjs: build-wasm
#! /bin/bash
set -eo pipefail
pnpm install
# Removes a duplicate copy of the WASM binary if Vite is left to its devices.
perl -pi -e 's/new URL\(.*\)/new URL()/g' "{{justfile_directory()}}/harper-wasm/pkg/harper_wasm.js"
cd "{{justfile_directory()}}/packages/harper.js"
pnpm install
pnpm build
# Generate API reference
@ -105,6 +104,15 @@ build-chrome-plugin: build-harperjs
pnpm install
pnpm zip
test-chrome-plugin: build-chrome-plugin
#!/bin/bash
set -eo pipefail
pnpm install
cd "{{justfile_directory()}}/packages/chrome-plugin"
pnpm playwright install
pnpm test
# Run VSCode plugin unit and integration tests.
test-vscode:
#! /bin/bash
@ -240,7 +248,7 @@ dogfood:
done
# Test everything.
test: test-vscode test-harperjs test-obsidian
test: test-harperjs test-vscode test-obsidian test-chrome-plugin
cargo test
# Use `harper-cli` to parse a provided file and print out the resulting tokens.

View file

@ -23,3 +23,10 @@
secrets.*.js
public/wasm
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View file

@ -1,6 +0,0 @@
# Ignore artifacts:
build
coverage
node_modules
pnpm-lock.yaml
pnpm-workspace.yaml

View file

@ -1,10 +0,0 @@
{
"jsxSingleQuote": false,
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "lf",
"printWidth": 100,
"semi": false,
"tabWidth": 2,
"useTabs": false
}

View file

@ -15,14 +15,17 @@
"build": "vite build",
"preview": "vite preview",
"fmt": "prettier --write '**/*.{svelte,ts,json,css,scss,md}'",
"zip": "npm run build && node src/zip.js"
"zip": "npm run build && node src/zip.js",
"test": "playwright test"
},
"devDependencies": {
"@crxjs/vite-plugin": "^2.0.0-beta.26",
"@playwright/test": "^1.52.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/chrome": "^0.0.246",
"@types/jquery": "^3.5.32",
"@types/lodash-es": "^4.17.12",
"@types/node": "catalog:",
"@types/virtual-dom": "^2.1.4",
"flowbite": "^3.1.2",
"flowbite-svelte": "^0.44.18",

View file

@ -0,0 +1,29 @@
import { defineConfig, devices } from '@playwright/test';
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});

View file

@ -48,17 +48,22 @@ export default class Highlights {
this.renderBoxes.set(source, renderBox);
}
const host = renderBox.getShadowHost();
host.id = 'harper-highlight-host';
const rect = getInitialContainingRect(renderBox.getShadowHost());
if (rect != null) {
renderBox.getShadowHost().style.contain = 'layout';
renderBox.getShadowHost().style.position = 'fixed';
renderBox.getShadowHost().style.left = `${-rect.x}px`;
renderBox.getShadowHost().style.top = `${-rect.y}px`;
renderBox.getShadowHost().style.width = '100vw';
renderBox.getShadowHost().style.height = '100vh';
renderBox.getShadowHost().style.zIndex = '100';
renderBox.getShadowHost().style.pointerEvents = 'none';
const hostStyle = host.style;
hostStyle.contain = 'layout';
hostStyle.position = 'fixed';
hostStyle.left = `${-rect.x}px`;
hostStyle.top = `${-rect.y}px`;
hostStyle.width = '100vw';
hostStyle.height = '100vh';
hostStyle.zIndex = '100';
hostStyle.pointerEvents = 'none';
}
renderBox.render(this.renderTree(boxes));
@ -101,6 +106,7 @@ export default class Highlights {
zIndex: 10,
borderBottom: `2px solid ${lintKindColor(box.lint.lint_kind)}`,
},
id: 'harper-highlight',
},
[],
);

View file

@ -47,8 +47,6 @@ export default class LintFramework {
let text: string | null = null;
if (!document.contains(target)) {
console.log('Removing target because it has left the document.', target);
console.log(`There are ${this.targets.size} targets left.`);
this.targets.delete(target);
continue;
}

View file

@ -22,7 +22,7 @@ export default class RenderBox {
public render(node: VNode) {
if (!this.virtualRoot || !this.virtualTree) {
this.virtualRoot = createElement(node);
const shadow = this.shadowHost.attachShadow({ mode: 'closed' });
const shadow = this.shadowHost.attachShadow({ mode: 'open' });
shadow.appendChild(this.virtualRoot);
} else {
const patches = diff(this.virtualTree, node);

View file

@ -0,0 +1,30 @@
import path from 'path';
import { type BrowserContext, test as base, chromium } from '@playwright/test';
export const test = base.extend<{
context: BrowserContext;
extensionId: string;
}>({
// biome-ignore lint/correctness/noEmptyPattern: it's by Playwright. Explanation not provided.
context: async ({}, use) => {
const pathToExtension = path.join(import.meta.dirname, '../build');
console.log(`Loading extension from ${pathToExtension}`);
const context = await chromium.launchPersistentContext('', {
channel: 'chromium',
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
});
await use(context);
await context.close();
},
extensionId: async ({ context }, use) => {
let [background] = context.serviceWorkers();
if (!background) background = await context.waitForEvent('serviceworker');
const extensionId = background.url().split('/')[2];
await use(extensionId);
},
});
export const expect = test.expect;

View file

@ -0,0 +1,75 @@
import type { Locator, Page } from '@playwright/test';
import { expect, test } from './fixtures';
/** Locates the first Harper highlight on the page and clicks it.
* It should result in the popup opening.
* Returns whether the highlight was found. */
async function clickHarperHighlight(page: Page): Promise<boolean> {
const highlight = page.locator('#harper-highlight');
if ((await highlight.count()) == 0) {
return false;
}
const box = await highlight.boundingBox();
if (box == null) {
return false;
}
// Locate the center of the element.
const cx = box.x + box.width / 2;
const cy = box.y + box.height / 2;
await page.mouse.click(cx, cy);
return true;
}
function getSlateEditor(page: Page): Locator {
return page.locator('[data-slate-editor="true"]');
}
async function replaceSlateContent(page: Page, text: string) {
const slateEditor = getSlateEditor(page);
await slateEditor.selectText();
await slateEditor.press('Backspace');
await slateEditor.pressSequentially(text);
}
test('Can apply basic suggestion.', async ({ page }) => {
await page.goto('https://slatejs.org');
await replaceSlateContent(page, 'This is an test');
await page.waitForTimeout(3000);
await clickHarperHighlight(page);
await page.getByTitle('Replace with "a"').click();
await page.waitForTimeout(3000);
const slateEditor = getSlateEditor(page);
expect(slateEditor).toContainText('This is a test');
// Slate has be known to revert changes after typing some more.
await slateEditor.pressSequentially(" of Harper's grammar checking.");
expect(slateEditor).toContainText("This is a test of Harper's grammar checking.");
});
test('Can ignore suggestion.', async ({ page }) => {
await page.goto('https://slatejs.org');
await replaceSlateContent(page, 'This is an test.');
await page.waitForTimeout(3000);
await clickHarperHighlight(page);
await page.getByTitle('Ignore this lint').click();
await page.waitForTimeout(3000);
// Nothing should change.
expect(getSlateEditor(page)).toContainText('This is an test');
expect(await clickHarperHighlight(page)).toBe(false);
});

52
pnpm-lock.yaml generated
View file

@ -68,6 +68,9 @@ importers:
'@crxjs/vite-plugin':
specifier: ^2.0.0-beta.26
version: 2.0.0-beta.32
'@playwright/test':
specifier: ^1.52.0
version: 1.52.0
'@sveltejs/vite-plugin-svelte':
specifier: ^4.0.0
version: 4.0.4(svelte@5.21.0)(vite@5.4.17(@types/node@22.13.10)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0))
@ -80,6 +83,9 @@ importers:
'@types/lodash-es':
specifier: ^4.17.12
version: 4.17.12
'@types/node':
specifier: 'catalog:'
version: 22.13.10
'@types/virtual-dom':
specifier: ^2.1.4
version: 2.1.4
@ -133,7 +139,7 @@ importers:
version: 7.52.1(@types/node@22.13.10)
'@vitest/browser':
specifier: ^3.0.6
version: 3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.51.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.8)
version: 3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.52.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.8)
harper-wasm:
specifier: workspace:*
version: link:../../harper-wasm/pkg
@ -145,7 +151,7 @@ importers:
version: 7.1.1
playwright:
specifier: ^1.49.1
version: 1.51.0
version: 1.52.0
type-fest:
specifier: ^4.37.0
version: 4.37.0
@ -227,7 +233,7 @@ importers:
version: 4.17.12
'@vitest/browser':
specifier: ^3.0.6
version: 3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.51.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.8)
version: 3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.52.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.8)
obsidian:
specifier: ^1.7.2
version: 1.8.7(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)
@ -386,7 +392,7 @@ importers:
devDependencies:
'@wordpress/scripts':
specifier: ^30.9.0
version: 30.13.0(@playwright/test@1.51.0)(@types/eslint@9.6.1)(@types/node@22.13.10)(@types/webpack@4.41.40)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.37.0)(typescript@5.8.2)
version: 30.13.0(@playwright/test@1.52.0)(@types/eslint@9.6.1)(@types/node@22.13.10)(@types/webpack@4.41.40)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.37.0)(typescript@5.8.2)
'@wp-now/wp-now':
specifier: ^0.1.74
version: 0.1.74
@ -2251,8 +2257,8 @@ packages:
resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@playwright/test@1.51.0':
resolution: {integrity: sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==}
'@playwright/test@1.52.0':
resolution: {integrity: sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==}
engines: {node: '>=18'}
hasBin: true
@ -8646,13 +8652,13 @@ packages:
pkg-types@2.1.0:
resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==}
playwright-core@1.51.0:
resolution: {integrity: sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg==}
playwright-core@1.52.0:
resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==}
engines: {node: '>=18'}
hasBin: true
playwright@1.51.0:
resolution: {integrity: sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA==}
playwright@1.52.0:
resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==}
engines: {node: '>=18'}
hasBin: true
@ -13998,9 +14004,9 @@ snapshots:
'@pkgr/core@0.1.1': {}
'@playwright/test@1.51.0':
'@playwright/test@1.52.0':
dependencies:
playwright: 1.51.0
playwright: 1.52.0
'@pmmmwh/react-refresh-webpack-plugin@0.5.15(@types/webpack@4.41.40)(react-refresh@0.14.2)(type-fest@4.37.0)(webpack-dev-server@4.15.2)(webpack@5.98.0)':
dependencies:
@ -15557,7 +15563,7 @@ snapshots:
tinyglobby: 0.2.12
vite-plugin-pwa: 0.21.1(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)
'@vitest/browser@3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.51.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.8)':
'@vitest/browser@3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.52.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.8)':
dependencies:
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0)
'@vitest/mocker': 3.0.8(msw@2.7.3(@types/node@22.13.10)(typescript@5.8.2))(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))
@ -15569,7 +15575,7 @@ snapshots:
vitest: 3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.29.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.8.2))(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
ws: 8.18.1
optionalDependencies:
playwright: 1.51.0
playwright: 1.52.0
transitivePeerDependencies:
- '@testing-library/dom'
- '@types/node'
@ -16261,9 +16267,9 @@ snapshots:
'@babel/runtime': 7.25.7
'@wordpress/deprecated': 4.20.0
'@wordpress/e2e-test-utils-playwright@1.20.0(@playwright/test@1.51.0)':
'@wordpress/e2e-test-utils-playwright@1.20.0(@playwright/test@1.52.0)':
dependencies:
'@playwright/test': 1.51.0
'@playwright/test': 1.52.0
change-case: 4.1.2
form-data: 4.0.2
get-port: 5.1.1
@ -16742,16 +16748,16 @@ snapshots:
react: 18.3.1
route-recognizer: 0.3.4
'@wordpress/scripts@30.13.0(@playwright/test@1.51.0)(@types/eslint@9.6.1)(@types/node@22.13.10)(@types/webpack@4.41.40)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.37.0)(typescript@5.8.2)':
'@wordpress/scripts@30.13.0(@playwright/test@1.52.0)(@types/eslint@9.6.1)(@types/node@22.13.10)(@types/webpack@4.41.40)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.37.0)(typescript@5.8.2)':
dependencies:
'@babel/core': 7.25.7
'@playwright/test': 1.51.0
'@playwright/test': 1.52.0
'@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@4.41.40)(react-refresh@0.14.2)(type-fest@4.37.0)(webpack-dev-server@4.15.2)(webpack@5.98.0)
'@svgr/webpack': 8.1.0(typescript@5.8.2)
'@wordpress/babel-preset-default': 8.20.0
'@wordpress/browserslist-config': 6.20.0
'@wordpress/dependency-extraction-webpack-plugin': 6.20.0(webpack@5.98.0)
'@wordpress/e2e-test-utils-playwright': 1.20.0(@playwright/test@1.51.0)
'@wordpress/e2e-test-utils-playwright': 1.20.0(@playwright/test@1.52.0)
'@wordpress/eslint-plugin': 22.6.0(@babel/core@7.25.7)(@types/eslint@9.6.1)(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.10)(babel-plugin-macros@3.1.0))(typescript@5.8.2)(wp-prettier@3.0.3)
'@wordpress/jest-preset-default': 12.20.0(@babel/core@7.25.7)(jest@29.7.0(@types/node@22.13.10)(babel-plugin-macros@3.1.0))
'@wordpress/npm-package-json-lint-config': 5.20.0(npm-package-json-lint@6.4.0(typescript@5.8.2))
@ -22288,11 +22294,11 @@ snapshots:
exsolve: 1.0.4
pathe: 2.0.3
playwright-core@1.51.0: {}
playwright-core@1.52.0: {}
playwright@1.51.0:
playwright@1.52.0:
dependencies:
playwright-core: 1.51.0
playwright-core: 1.52.0
optionalDependencies:
fsevents: 2.3.2
@ -24734,7 +24740,7 @@ snapshots:
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 22.13.10
'@vitest/browser': 3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.51.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.8)
'@vitest/browser': 3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.52.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.8)
jsdom: 20.0.3
transitivePeerDependencies:
- jiti