mirror of
https://github.com/penpot/penpot.git
synced 2026-08-01 10:56:20 +00:00
Merge pull request #6646 from penpot/ladybenko-10904-playwright-wasm
🔧 Set up visual regression tests for wasm renderer
This commit is contained in:
commit
e91550cd9d
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@ -11,3 +11,4 @@ node_modules/
|
|||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
/playwright/**/visual-specs/**/*.png
|
/playwright/**/visual-specs/**/*.png
|
||||||
|
|
||||||
|
|||||||
@ -53,6 +53,21 @@ export default defineConfig({
|
|||||||
toHaveScreenshot: { maxDiffPixelRatio: 0.005 },
|
toHaveScreenshot: { maxDiffPixelRatio: 0.005 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "render-wasm",
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
viewport: { width: 1920, height: 1080 }, // Add custom viewport size
|
||||||
|
deviceScaleFactor: 2,
|
||||||
|
},
|
||||||
|
testDir: "./playwright/ui/render-wasm-specs",
|
||||||
|
snapshotPathTemplate: "{testDir}/{testFilePath}-snapshots/{arg}.png",
|
||||||
|
expect: {
|
||||||
|
toHaveScreenshot: {
|
||||||
|
maxDiffPixelRatio: 0.005,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
/* Run your local dev server before starting the tests */
|
||||||
|
|||||||
BIN
frontend/playwright/data/render-wasm/assets/penguins.jpg
Normal file
BIN
frontend/playwright/data/render-wasm/assets/penguins.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 964 KiB |
1047
frontend/playwright/data/render-wasm/get-file-shapes-fills.json
Normal file
1047
frontend/playwright/data/render-wasm/get-file-shapes-fills.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1210
frontend/playwright/data/render-wasm/get-file-shapes-strokes.json
Normal file
1210
frontend/playwright/data/render-wasm/get-file-shapes-strokes.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,33 @@ export class BasePage {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async mockAsset(page, assetId, assetFilename, options) {
|
||||||
|
const ids = Array.isArray(assetId) ? assetId : [assetId];
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const url = `**/assets/by-file-media-id/${id}`;
|
||||||
|
|
||||||
|
await page.route(url, (route) =>
|
||||||
|
route.fulfill({
|
||||||
|
path: `playwright/data/${assetFilename}`,
|
||||||
|
status: 200,
|
||||||
|
...options,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async mockConfigFlags(page, flags) {
|
||||||
|
const url = "**/js/config.js?ts=*";
|
||||||
|
return await page.route(url, (route) =>
|
||||||
|
route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: "application/javascript",
|
||||||
|
body: `var penpotFlags = "${flags.join(" ")}";`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#page = null;
|
#page = null;
|
||||||
|
|
||||||
constructor(page) {
|
constructor(page) {
|
||||||
@ -38,14 +65,11 @@ export class BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async mockConfigFlags(flags) {
|
async mockConfigFlags(flags) {
|
||||||
const url = "**/js/config.js?ts=*";
|
return BasePage.mockConfigFlags(this.page, flags);
|
||||||
return await this.page.route(url, (route) =>
|
}
|
||||||
route.fulfill({
|
|
||||||
status: 200,
|
async mockAsset(assetId, assetFilename, options) {
|
||||||
contentType: "application/javascript",
|
return BasePage.mockAsset(this.page, assetId, assetFilename, options);
|
||||||
body: `var penpotFlags = "${flags.join(" ")}";`,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
frontend/playwright/ui/pages/WasmWorkspacePage.js
Normal file
31
frontend/playwright/ui/pages/WasmWorkspacePage.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { expect } from "@playwright/test";
|
||||||
|
import { WorkspacePage } from "./WorkspacePage";
|
||||||
|
|
||||||
|
export class WasmWorkspacePage extends WorkspacePage {
|
||||||
|
static async init(page) {
|
||||||
|
await super.init(page);
|
||||||
|
await WorkspacePage.mockConfigFlags(page, [
|
||||||
|
"enable-feature-render-wasm",
|
||||||
|
"enable-render-wasm-dpr",
|
||||||
|
]);
|
||||||
|
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
document.addEventListener("wasm:set-objects-finished", () => {
|
||||||
|
window.wasmSetObjectsFinished = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
this.canvas = page.getByTestId("canvas-wasm-shapes");
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitForFirstRender() {
|
||||||
|
await expect(this.pageName).toHaveText("Page 1");
|
||||||
|
await this.canvas.waitFor({ state: "visible" });
|
||||||
|
await this.page.waitForFunction(() => {
|
||||||
|
return window.wasmSetObjectsFinished;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -178,6 +178,10 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async mockGetFile(jsonFile) {
|
||||||
|
await this.mockRPC(/get\-file\?/, jsonFile);
|
||||||
|
}
|
||||||
|
|
||||||
async setupFileWithComments() {
|
async setupFileWithComments() {
|
||||||
await this.mockRPC(
|
await this.mockRPC(
|
||||||
"get-comment-threads?file-id=*",
|
"get-comment-threads?file-id=*",
|
||||||
|
|||||||
71
frontend/playwright/ui/render-wasm-specs/shapes.spec.js
Normal file
71
frontend/playwright/ui/render-wasm-specs/shapes.spec.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await WasmWorkspacePage.init(page);
|
||||||
|
await WasmWorkspacePage.mockConfigFlags(page, [
|
||||||
|
"enable-feature-render-wasm",
|
||||||
|
"enable-render-wasm-dpr",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Renders a file with basic shapes, boards and groups", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const workspace = new WasmWorkspacePage(page);
|
||||||
|
await workspace.setupEmptyFile();
|
||||||
|
await workspace.mockGetFile("render-wasm/get-file-shapes-groups-boards.json");
|
||||||
|
|
||||||
|
await workspace.goToWorkspace({
|
||||||
|
id: "53a7ff09-2228-81d3-8006-4b5eac177245",
|
||||||
|
pageId: "53a7ff09-2228-81d3-8006-4b5eac177246",
|
||||||
|
});
|
||||||
|
await workspace.waitForFirstRender();
|
||||||
|
|
||||||
|
await expect(workspace.canvas).toHaveScreenshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Renders a file with solid, gradient and image fills", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const workspace = new WasmWorkspacePage(page);
|
||||||
|
await workspace.setupEmptyFile();
|
||||||
|
await workspace.mockAsset(
|
||||||
|
[
|
||||||
|
"1ebcea38-f1bf-8101-8006-4c8fd68e7c84",
|
||||||
|
"1ebcea38-f1bf-8101-8006-4c8f579da49c",
|
||||||
|
],
|
||||||
|
"render-wasm/assets/penguins.jpg",
|
||||||
|
);
|
||||||
|
await workspace.mockGetFile("render-wasm/get-file-shapes-fills.json");
|
||||||
|
|
||||||
|
await workspace.goToWorkspace({
|
||||||
|
id: "1ebcea38-f1bf-8101-8006-4c8ec4a9bffe",
|
||||||
|
pageId: "1ebcea38-f1bf-8101-8006-4c8ec4a9bfff",
|
||||||
|
});
|
||||||
|
await workspace.waitForFirstRender();
|
||||||
|
|
||||||
|
await expect(workspace.canvas).toHaveScreenshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Renders a file with strokes", async ({ page }) => {
|
||||||
|
const workspace = new WasmWorkspacePage(page);
|
||||||
|
await workspace.setupEmptyFile();
|
||||||
|
await workspace.mockAsset(
|
||||||
|
[
|
||||||
|
"202c1104-9385-81d3-8006-5074e4682cac",
|
||||||
|
"202c1104-9385-81d3-8006-5074c50339b6",
|
||||||
|
"202c1104-9385-81d3-8006-507560ce29e3",
|
||||||
|
],
|
||||||
|
"render-wasm/assets/penguins.jpg",
|
||||||
|
);
|
||||||
|
await workspace.mockGetFile("render-wasm/get-file-shapes-strokes.json");
|
||||||
|
|
||||||
|
await workspace.goToWorkspace({
|
||||||
|
id: "202c1104-9385-81d3-8006-507413ff2c99",
|
||||||
|
pageId: "202c1104-9385-81d3-8006-507413ff2c9a",
|
||||||
|
});
|
||||||
|
await workspace.waitForFirstRender();
|
||||||
|
|
||||||
|
await expect(workspace.canvas).toHaveScreenshot();
|
||||||
|
});
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 169 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@ -361,6 +361,7 @@
|
|||||||
:viewport-ref viewport-ref}])]
|
:viewport-ref viewport-ref}])]
|
||||||
|
|
||||||
[:canvas {:id "render"
|
[:canvas {:id "render"
|
||||||
|
:data-testid "canvas-wasm-shapes"
|
||||||
:ref canvas-ref
|
:ref canvas-ref
|
||||||
:class (stl/css :render-shapes)
|
:class (stl/css :render-shapes)
|
||||||
:key (dm/str "render" page-id)
|
:key (dm/str "render" page-id)
|
||||||
|
|||||||
@ -766,13 +766,17 @@
|
|||||||
|
|
||||||
(defn process-pending
|
(defn process-pending
|
||||||
[pending]
|
[pending]
|
||||||
(when-let [pending (-> (d/index-by :key :callback pending) vals)]
|
(let [event (js/CustomEvent. "wasm:set-objects-finished")
|
||||||
(->> (rx/from pending)
|
pending (-> (d/index-by :key :callback pending) vals)]
|
||||||
(rx/mapcat (fn [callback] (callback)))
|
(if (not-empty? pending)
|
||||||
(rx/reduce conj [])
|
(->> (rx/from pending)
|
||||||
(rx/subs! (fn [_]
|
(rx/mapcat (fn [callback] (callback)))
|
||||||
(clear-drawing-cache)
|
(rx/reduce conj [])
|
||||||
(request-render "set-objects"))))))
|
(rx/subs! (fn [_]
|
||||||
|
(clear-drawing-cache)
|
||||||
|
(request-render "set-objects")
|
||||||
|
(.dispatchEvent ^js js/document event))))
|
||||||
|
(.dispatchEvent ^js js/document event))))
|
||||||
|
|
||||||
(defn process-object
|
(defn process-object
|
||||||
[shape]
|
[shape]
|
||||||
|
|||||||
53
render-wasm/docs/visual_regression_tests.md
Normal file
53
render-wasm/docs/visual_regression_tests.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Visual regression tests
|
||||||
|
|
||||||
|
> ⚠️ At the time being, these tests are to be run _locally_. They are not
|
||||||
|
> executed in CI yet.
|
||||||
|
|
||||||
|
## Run the tests
|
||||||
|
|
||||||
|
The tests are located in their own Playwright project, `render-wasm`. To run them, go to the `frontend` dir and execute Playwright passing the `--project` flag:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
cd frontend
|
||||||
|
npx playwright test --ui --project=render-wasm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Write new tests
|
||||||
|
|
||||||
|
You need to add a new spec file to `frontend/playwright/ui/render-wasm-specs` or add a test to one of the existing specs. You can use `shapes.spec.js` as reference.
|
||||||
|
|
||||||
|
Writing the tests is very similar to write any other test for Penpot. However, some helpers have been added to address some issues specific to the new render engine.
|
||||||
|
|
||||||
|
### Step 1: Initialize the page
|
||||||
|
|
||||||
|
There is a page helper, `WasmWorkspacePage` that contains:
|
||||||
|
|
||||||
|
- Automatically setting the right flags to enable the new wasm renderer.
|
||||||
|
- A helper function to wait for the first render to happen, `waitForFirstRender`.
|
||||||
|
- A locator for the `<canvas>` element in the viewport, `canvas`.
|
||||||
|
|
||||||
|
> :⚠️: An await for `waitForFirstRender` is crucial, specially if the render depends on requests that are run in a promise, like fetching images or fonts (even if they are mocked/intercepted!).
|
||||||
|
|
||||||
|
### Step 2: Intercept requests
|
||||||
|
|
||||||
|
The main requests of the API to intercept are: `/get-file` and `/assets/by-file-media-id/`
|
||||||
|
|
||||||
|
If you disable the feature flag `fdata/pointer-map` in your local environment, you will get a JSON response from `/get-file` that does _not_ include fragments, so you will not have to mock `get-file-fragment` too.
|
||||||
|
|
||||||
|
For mocking the assets, a new helper has been added: `mockAsset`. This accepts either an asset ID or an array of IDs.
|
||||||
|
|
||||||
|
### Step 3: Go to workspace and take a screenshot
|
||||||
|
|
||||||
|
After calling `goToWorkspace`, you need to add this to ensure that the `<canvas>` has been drawn into:
|
||||||
|
|
||||||
|
```js
|
||||||
|
await workspace.waitForFirstRender();
|
||||||
|
```
|
||||||
|
|
||||||
|
To take a screenshot, simply call:
|
||||||
|
|
||||||
|
```js
|
||||||
|
await expect(workspace.canvas).toHaveScreenshot();
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the test will fail the very first time you call it, because it will have no screenshot to compare to. It should work on following runs.
|
||||||
Loading…
x
Reference in New Issue
Block a user