From d8920b8936c7ac0adbb62a1f6d590feceab2b35b Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 2 Nov 2023 18:39:03 -0400 Subject: [PATCH] feat: visual polling --- e2e/perf.spec.ts | 98 ++++++++++++++++-- .../changes-editor-spec-1-chromium-darwin.png | Bin 0 -> 35058 bytes package.json | 4 + yarn.lock | 31 ++++++ 4 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 e2e/perf.spec.ts-snapshots/changes-editor-spec-1-chromium-darwin.png diff --git a/e2e/perf.spec.ts b/e2e/perf.spec.ts index de489b2e7..9901d9fcc 100644 --- a/e2e/perf.spec.ts +++ b/e2e/perf.spec.ts @@ -1,5 +1,6 @@ -import { test, expect, type Page } from '@playwright/test'; - +import { test, expect, type Page, Locator } from '@playwright/test'; +import { PNG } from 'pngjs'; +import pixelmatch from 'pixelmatch'; import * as fs from 'fs'; const jsonString = fs.readFileSync('./e2e/spec.json', 'utf-8'); @@ -10,17 +11,98 @@ function delay(time: number) { }); } -test('changes editor spec', async ({ page }) => { - await page.goto('/'); +/** + * Compares two PNG files and returns true if they are the same. + */ +function isPngSame(newImg: Buffer, oldImgPath: string) { + const img1 = PNG.sync.read(newImg); + const img2 = PNG.sync.read(fs.readFileSync(oldImgPath)); + // check if the images have the same dimensions + if (img1.width !== img2.width || img1.height !== img2.height) return false; + + const { width, height } = img1; + const diff = new PNG({ width, height }); + const pixeldifference = pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 }); + // only write to file if there is a difference in the images + return pixeldifference === 0; +} + +/** + * This function changes the editor spec by pasting the given JSON string. + */ +async function changeEditorSpec(page: Page, jsonString: string) { await page.evaluate(jsonString => { navigator.clipboard.writeText(jsonString); + }, jsonString); - await delay(1000); await page.mouse.click(200, 200); - await page.getByRole('textbox', { name: 'Editor content;Press Alt+F1 for Accessibility Options.' }).press('Control+a'); + await page.getByRole('textbox', { name: 'Editor content;Press Alt+F1 for Accessibility Options.' }).press('Control+KeyA'); await page.keyboard.press('Backspace'); - // await delay(1000); - await page.getByRole('textbox', { name: 'Editor content;Press Alt+F1 for Accessibility Options.' }).press('Meta+v'); + + await delay(100); + await page.mouse.click(200, 200, { button: 'right' }); + await delay(100); // this is needed to wait for the context menu to appear + await page.getByRole('menuitem', { name: 'Paste' }).click(); +} + +/** + * This function polls until the screenshot of the given component matches the expected screenshot. + */ +async function pollUntilScreenshotMatchesExpected( + component: Locator, + page: Page, + expectedScreenshotPath: string, + timeout: number +) { + let screenshotMatchesExpected = false; + let timeElapsed = 0; + while (!screenshotMatchesExpected && timeElapsed < timeout) { + const screenshot = await component.screenshot(); + + screenshotMatchesExpected = isPngSame(screenshot, expectedScreenshotPath); + + if (!screenshotMatchesExpected) { + await page.waitForTimeout(50); // wait 50ms before polling again + timeElapsed += 50; + } + } + return timeElapsed; +} + +test('changes editor spec', async ({ page, context }) => { + await context.grantPermissions(['clipboard-read', 'clipboard-write']); + await page.goto('/'); + await changeEditorSpec(page, jsonString); // wait for network to go idle await page.waitForLoadState('networkidle'); + const gosComponent = page.getByLabel('Gosling visualization'); + + const matchTime = await pollUntilScreenshotMatchesExpected( + gosComponent, + page, + 'e2e/perf.spec.ts-snapshots/changes-editor-spec-1-chromium-darwin.png', + 10000 + ); + + const totalBlockingTime = await page.evaluate(() => { + return new Promise((resolve) => { + let totalBlockingTime = 0 + new PerformanceObserver(function (list) { + const perfEntries = list.getEntries() + for (const perfEntry of perfEntries) { + totalBlockingTime += perfEntry.duration - 50 + } + perfEntries.forEach((entry) => console.log(entry.toJSON())) + resolve(totalBlockingTime) + }).observe({ type: 'longtask', buffered: true }) + + // Resolve promise if there haven't been long tasks + setTimeout(() => resolve(totalBlockingTime), 5000) + }) + }) + + console.log('blocking', parseFloat(totalBlockingTime as string)) // 0 + console.log(matchTime); + await delay(10000); + }); diff --git a/e2e/perf.spec.ts-snapshots/changes-editor-spec-1-chromium-darwin.png b/e2e/perf.spec.ts-snapshots/changes-editor-spec-1-chromium-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..6efaa0b0b2bf8f98c9aaf9658af463c4a4285ead GIT binary patch literal 35058 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 z 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 z z%_M(N-sn#a`O$wGkl`{e}ZZ?UR@Kh-Nt@8Fx4T_t|p8g_~2u;*p znblQ**npGO?KNDM?l*Hwij9t7C-*x<*hlsj2nck9w3x73s78Y7EicZ^D|I_waUcEw z1{E2%V|q`u%4#XW?^7v(<7N>rO|!aX^1P zYYd)3j=1NIp^B``2)F6}AzCT3vpZVnt|F@RWBe8hxdknSP0Z}lf`q<0=lv!*y5fbg zjuCFkt7_Z4hGhAWu&Xu%U?Tc_`TOW>Xc>N*%)>LP!kTaLW5-Z&pbtgk_nG-!^5RN- zgKagQ%*VPvgQFP(`}6P+ZKs`Tq3@+0=4Kofr`irfo_{7zNsN~j0E}^!E<%t}jyW>A$SY9`*s_D9-ss#jhQG@V~;p^Up zA?VudjjS70IBA0`TA-apSuYjvcBX6yCOtcs<>o9IqABZ6d|{(}v+d7_X=^xl(%^#8*my;sXsH`JBLma~)S8o*R9n5M zoR1&b6jl?%x0#V>nbCzQ{)_aFm(5o-N#mAk;9y;j58(D@KAf~S-6Vku(^>b~w!vMy zpf?g;Ab$%2rryj;4l8rom z#YIHE;!S5K9iMuIlsc6osa}i#>~X@ksvI{QOfMuMG&>#O;}kVqFidgaUrfzfTb-OArDwgl5LLJWzDi7fc9m}q|R_yILh?is!L(xhW;>ecc)I$+%;w}Lo+ zLByviZZJ?w4iDC7QVP?^YQ-cR84ShNXh{}%dBXMX#FC0JC*z#IKU z2OpS0z>jZ^?SVu<#R>@v0aUBZN<_eG;Q?U(ec6ZuIK1#gv)hlqLcPjpES3I9Umhi4 zZ3xNl3S8fFKjqX;&}k3)bg{~KAo9Bnu=_xDb#+Da>$~4#rIx6w>fBL#ZY}|b?P`DI zcUq6V1W7KJqiOoAPdDVG046T3VW6YIlr_Qka#Z*=vfE~}!?SV-6(vCsSl?7@I)>5p zJYlb*qvJi{dvbcJ)$T$~5Eu3}d;b0PvBB+pb>KaVoC6BTJ&tQ zGwbVr3JOSoLz!jdiUtV53p7+!0w5x!}XL5aRq&aMse~p;n zoL-BG?eQ$u7)({`0jL-myDtP@AI^<|)5Q56*PTH;_y~Z7_E*n7Kp3Q=uRnQrJXgb0 z(%qeBJQQ2!eyKk?I{K%w(qt$W|7^W2DFTghbJqE#BXWcm8hFVV$PLXZb&!L}99bP5 zT;R}jW4;U^Jj`bFJ(gAb@q^jGxEyP{&%eK*z(Xd~)YO`E82aAl>~3ev zjtk7d&y6iDs_N>H(YmIs9@iw|;^Hj_Vs_xn*w6t&AfB5uaYzUpCTfJ3$8HQK zQXj>3He_GdJM_>%f6oV|)6@0k_o@RE zWS)L+Z*R{1eXkmb=kwDqE^-B4Fb_;8z#C5Yz5DpEUMG_%Y~aZ6|HgK znGDH*4%e>&+f7Ft8gb=5oLP5N3y$)NW+C=N{=EM4=OPcJRT+T5)78&qol`;+0Ma7k AX8-^I literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 298bf1536..c9d758fc0 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,8 @@ "@types/d3-selection": "^2.0.0", "@types/lodash-es": "^4.17.5", "@types/node": "^18.6.2", + "@types/pixelmatch": "^5.2.5", + "@types/pngjs": "^6.0.3", "@types/pubsub-js": "^1.8.2", "@types/rbush": "^3.0.0", "@types/react": "^18.2.0", @@ -115,7 +117,9 @@ "jsoncrush": "^1.1.6", "knip": "^2.30.0", "npm-run-all": "^4.1.5", + "pixelmatch": "^5.3.0", "pixi.js": "^6.3.0", + "pngjs": "^7.0.0", "prettier": "^2.0.5", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/yarn.lock b/yarn.lock index 19791dce3..a268dd359 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1201,6 +1201,20 @@ resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz#e4a932069db47bb3eabeb0b305502d01586fa90d" integrity sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg== +"@types/pixelmatch@^5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.5.tgz#65eb7f7076a93cc003a504e363d0bf403ecbeede" + integrity sha512-di/HknmWA+KNjlLczJiLft9g1mHJZl5qGAXtDct8KsJg8KPrXKJa8Avumj53fgdJOBbfHABYhp3EjyitmKPdBg== + dependencies: + "@types/node" "*" + +"@types/pngjs@^6.0.3": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-6.0.3.tgz#33bfafdae1e6803624357be49f27ea70581a199e" + integrity sha512-F/WaGVKEZ1XYFlEtsWtqWm92vRfQdOqSSTBPj07BRDKnDtRhCw50DpwEQtrrDwEZUoAZAzv2FaalZiNV/54BoQ== + dependencies: + "@types/node" "*" + "@types/prop-types@*": version "15.7.5" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" @@ -5341,6 +5355,13 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== +pixelmatch@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.3.0.tgz#5e5321a7abedfb7962d60dbf345deda87cb9560a" + integrity sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q== + dependencies: + pngjs "^6.0.0" + pixi.js@^6.3.0: version "6.5.9" resolved "https://registry.yarnpkg.com/pixi.js/-/pixi.js-6.5.9.tgz#c85fb0f7efa303c17d8edc2698ca4b1545b23ab0" @@ -5413,6 +5434,16 @@ playwright@1.39.0: optionalDependencies: fsevents "2.3.2" +pngjs@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821" + integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg== + +pngjs@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-7.0.0.tgz#a8b7446020ebbc6ac739db6c5415a65d17090e26" + integrity sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow== + postcss@^8.4.27: version "8.4.31" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"