diff --git a/resources/main.mjs b/resources/main.mjs index 76b756449..3b5f39c01 100644 --- a/resources/main.mjs +++ b/resources/main.mjs @@ -94,7 +94,7 @@ class MainBenchmarkClient { const enabledSuites = Suites.filter((suite) => !suite.disabled); const totalSubtestsCount = enabledSuites.reduce((testsCount, suite) => { - return testsCount + suite.tests.length; + return testsCount + (suite.tests?.length ?? suite.config?.steps); }, 0); this.stepCount = params.iterationCount * totalSubtestsCount; this._progressCompleted.max = this.stepCount; diff --git a/resources/params.mjs b/resources/params.mjs index fa3f8c3bf..f406c63f6 100644 --- a/resources/params.mjs +++ b/resources/params.mjs @@ -146,9 +146,14 @@ class Params { return shuffleSeed; } - toSearchParams() { + toSearchParams(forRemote = false) { const rawParams = { ...this }; rawParams["viewport"] = `${this.viewport.width}x${this.viewport.height}`; + + if (forRemote) { + delete rawParams["suites"]; + delete rawParams["tags"]; + } return new URLSearchParams(rawParams).toString(); } } diff --git a/resources/remote-helloworld/.gitignore b/resources/remote-helloworld/.gitignore new file mode 100644 index 000000000..251ce6d2b --- /dev/null +++ b/resources/remote-helloworld/.gitignore @@ -0,0 +1,23 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/resources/remote-helloworld/counter.js b/resources/remote-helloworld/counter.js new file mode 100644 index 000000000..0267c2da6 --- /dev/null +++ b/resources/remote-helloworld/counter.js @@ -0,0 +1,9 @@ +export function setupCounter(element) { + let counter = 0; + const setCounter = (count) => { + counter = count; + element.innerHTML = `count is ${counter}`; + }; + element.addEventListener("click", () => setCounter(counter + 1)); + setCounter(0); +} diff --git a/resources/remote-helloworld/dist/assets/index-D2lLi4lE.js b/resources/remote-helloworld/dist/assets/index-D2lLi4lE.js new file mode 100644 index 000000000..82b80ea2c --- /dev/null +++ b/resources/remote-helloworld/dist/assets/index-D2lLi4lE.js @@ -0,0 +1,412 @@ +var __defProp = Object.defineProperty; +var __typeError = (msg) => { + throw TypeError(msg); +}; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); +var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); +var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); +var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); +var _frame, _page, _params, _suite, _test, _callback; +function setupCounter(element) { + let counter = 0; + const setCounter = (count) => { + counter = count; + element.innerHTML = `count is ${counter}`; + }; + element.addEventListener("click", () => setCounter(counter + 1)); + setCounter(0); +} +class TestInvoker { + constructor(syncCallback, asyncCallback, reportCallback, params2) { + this._syncCallback = syncCallback; + this._asyncCallback = asyncCallback; + this._reportCallback = reportCallback; + this._params = params2; + } +} +class TimerTestInvoker extends TestInvoker { + start() { + return new Promise((resolve) => { + setTimeout(() => { + this._syncCallback(); + setTimeout(() => { + this._asyncCallback(); + requestAnimationFrame(async () => { + const result = await this._reportCallback(); + resolve(result); + }); + }, 0); + }, this._params.waitBeforeSync); + }); + } +} +class RAFTestInvoker extends TestInvoker { + start() { + return new Promise((resolve) => { + if (this._params.waitBeforeSync) + setTimeout(() => this._scheduleCallbacks(resolve), this._params.waitBeforeSync); + else + this._scheduleCallbacks(resolve); + }); + } + _scheduleCallbacks(resolve) { + requestAnimationFrame(() => this._syncCallback()); + requestAnimationFrame(() => { + setTimeout(() => { + this._asyncCallback(); + setTimeout(async () => { + const result = await this._reportCallback(); + resolve(result); + }, 0); + }, 0); + }); + } +} +const TEST_INVOKER_LOOKUP = { + __proto__: null, + timer: TimerTestInvoker, + raf: RAFTestInvoker +}; +class TestRunner { + constructor(frame, page, params2, suite, test, callback) { + __privateAdd(this, _frame); + __privateAdd(this, _page); + __privateAdd(this, _params); + __privateAdd(this, _suite); + __privateAdd(this, _test); + __privateAdd(this, _callback); + __privateSet(this, _suite, suite); + __privateSet(this, _test, test); + __privateSet(this, _params, params2); + __privateSet(this, _callback, callback); + __privateSet(this, _page, page); + __privateSet(this, _frame, frame); + } + async runTest() { + const suiteName = __privateGet(this, _suite).name; + const testName = __privateGet(this, _test).name; + const syncStartLabel = `${suiteName}.${testName}-start`; + const syncEndLabel = `${suiteName}.${testName}-sync-end`; + const asyncStartLabel = `${suiteName}.${testName}-async-start`; + const asyncEndLabel = `${suiteName}.${testName}-async-end`; + let syncTime; + let asyncStartTime; + let asyncTime; + const runSync = () => { + if (__privateGet(this, _params).warmupBeforeSync) { + performance.mark("warmup-start"); + const startTime = performance.now(); + while (performance.now() - startTime < __privateGet(this, _params).warmupBeforeSync) + continue; + performance.mark("warmup-end"); + } + performance.mark(syncStartLabel); + const syncStartTime = performance.now(); + __privateGet(this, _test).run(__privateGet(this, _page)); + const syncEndTime = performance.now(); + performance.mark(syncEndLabel); + syncTime = syncEndTime - syncStartTime; + performance.mark(asyncStartLabel); + asyncStartTime = performance.now(); + }; + const measureAsync = () => { + const bodyReference = __privateGet(this, _frame) ? __privateGet(this, _frame).contentDocument.body : document.body; + const windowReference = __privateGet(this, _frame) ? __privateGet(this, _frame).contentWindow : window; + const height = bodyReference.getBoundingClientRect().height; + windowReference._unusedHeightValue = height; + const asyncEndTime = performance.now(); + performance.mark(asyncEndLabel); + asyncTime = asyncEndTime - asyncStartTime; + if (__privateGet(this, _params).warmupBeforeSync) + performance.measure("warmup", "warmup-start", "warmup-end"); + performance.measure(`${suiteName}.${testName}-sync`, syncStartLabel, syncEndLabel); + performance.measure(`${suiteName}.${testName}-async`, asyncStartLabel, asyncEndLabel); + }; + const report = () => __privateGet(this, _callback).call(this, __privateGet(this, _test), syncTime, asyncTime); + const invokerClass = TEST_INVOKER_LOOKUP[__privateGet(this, _params).measurementMethod]; + const invoker = new invokerClass(runSync, measureAsync, report, __privateGet(this, _params)); + return invoker.start(); + } +} +_frame = new WeakMap(); +_page = new WeakMap(); +_params = new WeakMap(); +_suite = new WeakMap(); +_test = new WeakMap(); +_callback = new WeakMap(); +class Params { + constructor(searchParams2 = void 0) { + __publicField(this, "viewport", { + width: 800, + height: 600 + }); + // Enable a detailed developer menu to change the current Params. + __publicField(this, "developerMode", false); + __publicField(this, "startAutomatically", false); + __publicField(this, "iterationCount", 10); + __publicField(this, "suites", []); + // A list of tags to filter suites + __publicField(this, "tags", []); + // Toggle running a dummy suite once before the normal test suites. + __publicField(this, "useWarmupSuite", false); + // Change how a test measurement is triggered and async time is measured: + // "timer": The classic (as in Speedometer 2.x) way using setTimeout + // "raf": Using rAF callbacks, both for triggering the sync part and for measuring async time. + __publicField(this, "measurementMethod", "raf"); + // or "timer" + // Wait time before the sync step in ms. + __publicField(this, "waitBeforeSync", 0); + // Warmup time before the sync step in ms. + __publicField(this, "warmupBeforeSync", 0); + // Seed for shuffling the execution order of suites. + // "off": do not shuffle + // "generate": generate a random seed + // : use the provided integer as a seed + __publicField(this, "shuffleSeed", "off"); + if (searchParams2) + this._copyFromSearchParams(searchParams2); + if (!this.developerMode) { + Object.freeze(this.viewport); + Object.freeze(this); + } + } + _parseInt(value, errorMessage) { + const number = Number(value); + if (!Number.isInteger(number) && errorMessage) + throw new Error(`Invalid ${errorMessage} param: '${value}', expected int.`); + return parseInt(number); + } + _copyFromSearchParams(searchParams2) { + this.viewport = this._parseViewport(searchParams2); + this.startAutomatically = this._parseBooleanParam(searchParams2, "startAutomatically"); + this.iterationCount = this._parseIntParam(searchParams2, "iterationCount", 1); + this.suites = this._parseSuites(searchParams2); + this.tags = this._parseTags(searchParams2); + this.developerMode = this._parseBooleanParam(searchParams2, "developerMode"); + this.useWarmupSuite = this._parseBooleanParam(searchParams2, "useWarmupSuite"); + this.waitBeforeSync = this._parseIntParam(searchParams2, "waitBeforeSync", 0); + this.warmupBeforeSync = this._parseIntParam(searchParams2, "warmupBeforeSync", 0); + this.measurementMethod = this._parseMeasurementMethod(searchParams2); + this.shuffleSeed = this._parseShuffleSeed(searchParams2); + const unused = Array.from(searchParams2.keys()); + if (unused.length > 0) + console.error("Got unused search params", unused); + } + _parseBooleanParam(searchParams2, paramKey) { + if (!searchParams2.has(paramKey)) + return false; + searchParams2.delete(paramKey); + return true; + } + _parseIntParam(searchParams2, paramKey, minValue) { + if (!searchParams2.has(paramKey)) + return defaultParams[paramKey]; + const parsedValue = this._parseInt(searchParams2.get(paramKey), "waitBeforeSync"); + if (parsedValue < minValue) + throw new Error(`Invalid ${paramKey} param: '${parsedValue}', value must be >= ${minValue}.`); + searchParams2.delete(paramKey); + return parsedValue; + } + _parseViewport(searchParams2) { + if (!searchParams2.has("viewport")) + return defaultParams.viewport; + const viewportParam = searchParams2.get("viewport"); + const [width, height] = viewportParam.split("x"); + const viewport = { + width: this._parseInt(width, "viewport.width"), + height: this._parseInt(height, "viewport.height") + }; + if (this.viewport.width < 800 || this.viewport.height < 600) + throw new Error(`Invalid viewport param: ${viewportParam}`); + searchParams2.delete("viewport"); + return viewport; + } + _parseSuites(searchParams2) { + if (searchParams2.has("suite") || searchParams2.has("suites")) { + if (searchParams2.has("suite") && searchParams2.has("suites")) + throw new Error("Params 'suite' and 'suites' can not be used together."); + const value = searchParams2.get("suite") || searchParams2.get("suites"); + const suites = value.split(","); + if (suites.length === 0) + throw new Error("No suites selected"); + searchParams2.delete("suite"); + searchParams2.delete("suites"); + return suites; + } + return defaultParams.suites; + } + _parseTags() { + if (!searchParams.has("tags")) + return defaultParams.tags; + if (this.suites.length) + throw new Error("'suites' and 'tags' cannot be used together."); + const tags = searchParams.get("tags").split(","); + searchParams.delete("tags"); + return tags; + } + _parseMeasurementMethod(searchParams2) { + if (!searchParams2.has("measurementMethod")) + return defaultParams.measurementMethod; + const measurementMethod = searchParams2.get("measurementMethod"); + if (measurementMethod !== "timer" && measurementMethod !== "raf") + throw new Error(`Invalid measurement method: '${measurementMethod}', must be either 'raf' or 'timer'.`); + searchParams2.delete("measurementMethod"); + return measurementMethod; + } + _parseShuffleSeed(searchParams2) { + if (!searchParams2.has("shuffleSeed")) + return defaultParams.shuffleSeed; + let shuffleSeed = searchParams2.get("shuffleSeed"); + if (shuffleSeed !== "off") { + if (shuffleSeed === "generate") { + shuffleSeed = Math.floor(Math.random() * 1 << 16); + console.log(`Generated a random suite order seed: ${shuffleSeed}`); + } else { + shuffleSeed = parseInt(shuffleSeed); + } + if (!Number.isInteger(shuffleSeed)) + throw new Error(`Invalid shuffle seed: '${shuffleSeed}', must be either 'off', 'generate' or an integer.`); + } + searchParams2.delete("shuffleSeed"); + return shuffleSeed; + } + toSearchParams(forRemote = false) { + const rawParams = { ...this }; + rawParams["viewport"] = `${this.viewport.width}x${this.viewport.height}`; + if (forRemote) { + delete rawParams["suites"]; + delete rawParams["tags"]; + } + return new URLSearchParams(rawParams).toString(); + } +} +const defaultParams = new Params(); +const searchParams = new URLSearchParams(window.location.search); +let maybeCustomParams = new Params(); +try { + maybeCustomParams = new Params(searchParams); +} catch (e) { + console.error("Invalid URL Param", e, "\nUsing defaults as fallback:", maybeCustomParams); + alert(`Invalid URL Param: ${e}`); +} +const params = maybeCustomParams; +class BenchmarkTestStep { + constructor(name, run) { + this.name = name; + this.run = run; + } + async runAndRecord(params2, suite, test, callback) { + const testRunner = new TestRunner(null, null, params2, suite, test, callback); + const result = await testRunner.runTest(); + return result; + } +} +class BenchmarkTestSuite { + constructor(name, tests) { + this.name = name; + this.tests = tests; + } + record(_test2, syncTime, asyncTime) { + const total = syncTime + asyncTime; + const results = { + tests: { Sync: syncTime, Async: asyncTime }, + total + }; + return results; + } + async runAndRecord(params2, onProgress) { + const measuredValues = { + tests: {}, + total: 0 + }; + const suiteStartLabel = `suite-${this.name}-start`; + const suiteEndLabel = `suite-${this.name}-end`; + performance.mark(suiteStartLabel); + for (const test of this.tests) { + const result = await test.runAndRecord(params2, this, test, this.record); + measuredValues.tests[test.name] = result; + measuredValues.total += result.total; + onProgress == null ? void 0 : onProgress(test.name); + } + performance.mark(suiteEndLabel); + performance.measure(`suite-${this.name}`, suiteStartLabel, suiteEndLabel); + return { + type: "suite-tests-complete", + status: "success", + result: measuredValues, + suitename: this.name + }; + } +} +class BenchmarkSuitesManager { + constructor(name, suites) { + this.name = name; + this.suites = suites; + } + getSuiteByName(name) { + return this.suites.find((suite) => suite.name === name); + } +} +function getParent(lookupStartNode, path) { + lookupStartNode = lookupStartNode.shadowRoot ?? lookupStartNode; + const parent = path.reduce((root, selector) => { + const node = root.querySelector(selector); + return node.shadowRoot ?? node; + }, lookupStartNode); + return parent; +} +function getElement(selector, path = [], lookupStartNode = document) { + const element = getParent(lookupStartNode, path).querySelector(selector); + return element; +} +function forceLayout() { + const rect = document.body.getBoundingClientRect(); + const e = document.elementFromPoint(rect.width / 2 | 0, rect.height / 2 | 0); + return e; +} +function connectFromRemote(name, version) { + const appId = `${name}-${version}`; + function sendMessage(message) { + window.top.postMessage(message, "*"); + } + window.onmessage = async (event) => { + if (event.data.id !== appId || event.data.key !== "benchmark-connector") + return; + switch (event.data.type) { + case "benchmark-suite": + const { result } = await window.benchmarkSuitesManager.getSuiteByName(event.data.name).runAndRecord(params, (test) => sendMessage({ type: "step-complete", status: "success", appId, name, test })); + sendMessage({ type: "suite-complete", status: "success", appId, result }); + break; + } + }; + sendMessage({ type: "app-ready", status: "success", appId }); +} +window.benchmarkSuitesManager = new BenchmarkSuitesManager(window.name, [ + new BenchmarkTestSuite("default", [ + new BenchmarkTestStep("Click counter (1000)", () => { + for (let i = 0; i < 1e3; i++) { + getElement("#counter").click(); + forceLayout(); + } + }), + new BenchmarkTestStep("Click counter (5000)", () => { + for (let i = 0; i < 5e3; i++) { + getElement("#counter").click(); + forceLayout(); + } + }) + ]) +]); +document.querySelector("#app").innerHTML = ` +
+

Remote Workload - Hello World

+
+ +
+
+`; +setupCounter(document.querySelector("#counter")); +connectFromRemote("remote-hello-world", 1); +//# sourceMappingURL=index-D2lLi4lE.js.map diff --git a/resources/remote-helloworld/dist/assets/index-D2lLi4lE.js.map b/resources/remote-helloworld/dist/assets/index-D2lLi4lE.js.map new file mode 100644 index 000000000..49d23751e --- /dev/null +++ b/resources/remote-helloworld/dist/assets/index-D2lLi4lE.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index-D2lLi4lE.js","sources":["../../counter.js","../../../test-invoker.mjs","../../../test-runner.mjs","../../../params.mjs","../../../workload-testing-utils.mjs","../../main.js"],"sourcesContent":["export function setupCounter(element) {\n let counter = 0;\n const setCounter = (count) => {\n counter = count;\n element.innerHTML = `count is ${counter}`;\n };\n element.addEventListener(\"click\", () => setCounter(counter + 1));\n setCounter(0);\n}\n","class TestInvoker {\n constructor(syncCallback, asyncCallback, reportCallback, params) {\n this._syncCallback = syncCallback;\n this._asyncCallback = asyncCallback;\n this._reportCallback = reportCallback;\n this._params = params;\n }\n}\n\nexport class TimerTestInvoker extends TestInvoker {\n start() {\n return new Promise((resolve) => {\n setTimeout(() => {\n this._syncCallback();\n setTimeout(() => {\n this._asyncCallback();\n requestAnimationFrame(async () => {\n const result = await this._reportCallback();\n resolve(result);\n });\n }, 0);\n }, this._params.waitBeforeSync);\n });\n }\n}\n\nexport class RAFTestInvoker extends TestInvoker {\n start() {\n return new Promise((resolve) => {\n if (this._params.waitBeforeSync)\n setTimeout(() => this._scheduleCallbacks(resolve), this._params.waitBeforeSync);\n else\n this._scheduleCallbacks(resolve);\n });\n }\n\n _scheduleCallbacks(resolve) {\n requestAnimationFrame(() => this._syncCallback());\n requestAnimationFrame(() => {\n setTimeout(() => {\n this._asyncCallback();\n setTimeout(async () => {\n const result = await this._reportCallback();\n resolve(result);\n }, 0);\n }, 0);\n });\n }\n}\n\nexport const TEST_INVOKER_LOOKUP = {\n __proto__: null,\n timer: TimerTestInvoker,\n raf: RAFTestInvoker,\n};\n","import { TEST_INVOKER_LOOKUP } from \"./test-invoker.mjs\";\n\nexport class TestRunner {\n #frame;\n #page;\n #params;\n #suite;\n #test;\n #callback;\n\n constructor(frame, page, params, suite, test, callback) {\n this.#suite = suite;\n this.#test = test;\n this.#params = params;\n this.#callback = callback;\n\n this.#page = page;\n this.#frame = frame;\n }\n\n async runTest() {\n // Prepare all mark labels outside the measuring loop.\n const suiteName = this.#suite.name;\n const testName = this.#test.name;\n const syncStartLabel = `${suiteName}.${testName}-start`;\n const syncEndLabel = `${suiteName}.${testName}-sync-end`;\n const asyncStartLabel = `${suiteName}.${testName}-async-start`;\n const asyncEndLabel = `${suiteName}.${testName}-async-end`;\n\n let syncTime;\n let asyncStartTime;\n let asyncTime;\n\n const runSync = () => {\n if (this.#params.warmupBeforeSync) {\n performance.mark(\"warmup-start\");\n const startTime = performance.now();\n // Infinite loop for the specified ms.\n while (performance.now() - startTime < this.#params.warmupBeforeSync)\n continue;\n performance.mark(\"warmup-end\");\n }\n performance.mark(syncStartLabel);\n const syncStartTime = performance.now();\n this.#test.run(this.#page);\n const syncEndTime = performance.now();\n performance.mark(syncEndLabel);\n\n syncTime = syncEndTime - syncStartTime;\n\n performance.mark(asyncStartLabel);\n asyncStartTime = performance.now();\n };\n const measureAsync = () => {\n const bodyReference = this.#frame ? this.#frame.contentDocument.body : document.body;\n const windowReference = this.#frame ? this.#frame.contentWindow : window;\n // Some browsers don't immediately update the layout for paint.\n // Force the layout here to ensure we're measuring the layout time.\n const height = bodyReference.getBoundingClientRect().height;\n windowReference._unusedHeightValue = height; // Prevent dead code elimination.\n\n const asyncEndTime = performance.now();\n performance.mark(asyncEndLabel);\n\n asyncTime = asyncEndTime - asyncStartTime;\n\n if (this.#params.warmupBeforeSync)\n performance.measure(\"warmup\", \"warmup-start\", \"warmup-end\");\n performance.measure(`${suiteName}.${testName}-sync`, syncStartLabel, syncEndLabel);\n performance.measure(`${suiteName}.${testName}-async`, asyncStartLabel, asyncEndLabel);\n };\n\n const report = () => this.#callback(this.#test, syncTime, asyncTime);\n const invokerClass = TEST_INVOKER_LOOKUP[this.#params.measurementMethod];\n const invoker = new invokerClass(runSync, measureAsync, report, this.#params);\n\n return invoker.start();\n }\n}\n","class Params {\n viewport = {\n width: 800,\n height: 600,\n };\n // Enable a detailed developer menu to change the current Params.\n developerMode = false;\n startAutomatically = false;\n iterationCount = 10;\n suites = [];\n // A list of tags to filter suites\n tags = [];\n // Toggle running a dummy suite once before the normal test suites.\n useWarmupSuite = false;\n // Change how a test measurement is triggered and async time is measured:\n // \"timer\": The classic (as in Speedometer 2.x) way using setTimeout\n // \"raf\": Using rAF callbacks, both for triggering the sync part and for measuring async time.\n measurementMethod = \"raf\"; // or \"timer\"\n // Wait time before the sync step in ms.\n waitBeforeSync = 0;\n // Warmup time before the sync step in ms.\n warmupBeforeSync = 0;\n // Seed for shuffling the execution order of suites.\n // \"off\": do not shuffle\n // \"generate\": generate a random seed\n // : use the provided integer as a seed\n shuffleSeed = \"off\";\n\n constructor(searchParams = undefined) {\n if (searchParams)\n this._copyFromSearchParams(searchParams);\n if (!this.developerMode) {\n Object.freeze(this.viewport);\n Object.freeze(this);\n }\n }\n\n _parseInt(value, errorMessage) {\n const number = Number(value);\n if (!Number.isInteger(number) && errorMessage)\n throw new Error(`Invalid ${errorMessage} param: '${value}', expected int.`);\n return parseInt(number);\n }\n\n _copyFromSearchParams(searchParams) {\n this.viewport = this._parseViewport(searchParams);\n this.startAutomatically = this._parseBooleanParam(searchParams, \"startAutomatically\");\n this.iterationCount = this._parseIntParam(searchParams, \"iterationCount\", 1);\n this.suites = this._parseSuites(searchParams);\n this.tags = this._parseTags(searchParams);\n this.developerMode = this._parseBooleanParam(searchParams, \"developerMode\");\n this.useWarmupSuite = this._parseBooleanParam(searchParams, \"useWarmupSuite\");\n this.waitBeforeSync = this._parseIntParam(searchParams, \"waitBeforeSync\", 0);\n this.warmupBeforeSync = this._parseIntParam(searchParams, \"warmupBeforeSync\", 0);\n this.measurementMethod = this._parseMeasurementMethod(searchParams);\n this.shuffleSeed = this._parseShuffleSeed(searchParams);\n\n const unused = Array.from(searchParams.keys());\n if (unused.length > 0)\n console.error(\"Got unused search params\", unused);\n }\n\n _parseBooleanParam(searchParams, paramKey) {\n if (!searchParams.has(paramKey))\n return false;\n searchParams.delete(paramKey);\n return true;\n }\n\n _parseIntParam(searchParams, paramKey, minValue) {\n if (!searchParams.has(paramKey))\n return defaultParams[paramKey];\n\n const parsedValue = this._parseInt(searchParams.get(paramKey), \"waitBeforeSync\");\n if (parsedValue < minValue)\n throw new Error(`Invalid ${paramKey} param: '${parsedValue}', value must be >= ${minValue}.`);\n searchParams.delete(paramKey);\n return parsedValue;\n }\n\n _parseViewport(searchParams) {\n if (!searchParams.has(\"viewport\"))\n return defaultParams.viewport;\n const viewportParam = searchParams.get(\"viewport\");\n const [width, height] = viewportParam.split(\"x\");\n const viewport = {\n width: this._parseInt(width, \"viewport.width\"),\n height: this._parseInt(height, \"viewport.height\"),\n };\n if (this.viewport.width < 800 || this.viewport.height < 600)\n throw new Error(`Invalid viewport param: ${viewportParam}`);\n searchParams.delete(\"viewport\");\n return viewport;\n }\n\n _parseSuites(searchParams) {\n if (searchParams.has(\"suite\") || searchParams.has(\"suites\")) {\n if (searchParams.has(\"suite\") && searchParams.has(\"suites\"))\n throw new Error(\"Params 'suite' and 'suites' can not be used together.\");\n const value = searchParams.get(\"suite\") || searchParams.get(\"suites\");\n const suites = value.split(\",\");\n if (suites.length === 0)\n throw new Error(\"No suites selected\");\n searchParams.delete(\"suite\");\n searchParams.delete(\"suites\");\n return suites;\n }\n return defaultParams.suites;\n }\n\n _parseTags() {\n if (!searchParams.has(\"tags\"))\n return defaultParams.tags;\n if (this.suites.length)\n throw new Error(\"'suites' and 'tags' cannot be used together.\");\n const tags = searchParams.get(\"tags\").split(\",\");\n searchParams.delete(\"tags\");\n return tags;\n }\n\n _parseMeasurementMethod(searchParams) {\n if (!searchParams.has(\"measurementMethod\"))\n return defaultParams.measurementMethod;\n const measurementMethod = searchParams.get(\"measurementMethod\");\n if (measurementMethod !== \"timer\" && measurementMethod !== \"raf\")\n throw new Error(`Invalid measurement method: '${measurementMethod}', must be either 'raf' or 'timer'.`);\n searchParams.delete(\"measurementMethod\");\n return measurementMethod;\n }\n\n _parseShuffleSeed(searchParams) {\n if (!searchParams.has(\"shuffleSeed\"))\n return defaultParams.shuffleSeed;\n let shuffleSeed = searchParams.get(\"shuffleSeed\");\n if (shuffleSeed !== \"off\") {\n if (shuffleSeed === \"generate\") {\n shuffleSeed = Math.floor((Math.random() * 1) << 16);\n console.log(`Generated a random suite order seed: ${shuffleSeed}`);\n } else {\n shuffleSeed = parseInt(shuffleSeed);\n }\n if (!Number.isInteger(shuffleSeed))\n throw new Error(`Invalid shuffle seed: '${shuffleSeed}', must be either 'off', 'generate' or an integer.`);\n }\n searchParams.delete(\"shuffleSeed\");\n return shuffleSeed;\n }\n\n toSearchParams(forRemote = false) {\n const rawParams = { ...this };\n rawParams[\"viewport\"] = `${this.viewport.width}x${this.viewport.height}`;\n\n if (forRemote) {\n delete rawParams[\"suites\"];\n delete rawParams[\"tags\"];\n }\n return new URLSearchParams(rawParams).toString();\n }\n}\n\nexport const defaultParams = new Params();\n\nconst searchParams = new URLSearchParams(window.location.search);\nlet maybeCustomParams = new Params();\ntry {\n maybeCustomParams = new Params(searchParams);\n} catch (e) {\n console.error(\"Invalid URL Param\", e, \"\\nUsing defaults as fallback:\", maybeCustomParams);\n alert(`Invalid URL Param: ${e}`);\n}\nexport const params = maybeCustomParams;\n","/*\n This is loaded by the remote test page and provides utilities & client-side communication with the test runner.\n*/\n\nimport { TestRunner } from \"./test-runner.mjs\";\nimport { params } from \"./params.mjs\";\n\n/**\n * BenchmarkTestStep\n *\n * A single test step, with a common interface to interact with.\n */\nexport class BenchmarkTestStep {\n constructor(name, run) {\n this.name = name;\n this.run = run;\n }\n\n async runAndRecord(params, suite, test, callback) {\n const testRunner = new TestRunner(null, null, params, suite, test, callback);\n const result = await testRunner.runTest();\n return result;\n }\n}\n\n/**\n * BenchmarkTestSuite\n *\n * A single test suite that contains one or more test steps.\n */\nexport class BenchmarkTestSuite {\n constructor(name, tests) {\n this.name = name;\n this.tests = tests;\n }\n\n record(_test, syncTime, asyncTime) {\n const total = syncTime + asyncTime;\n const results = {\n tests: { Sync: syncTime, Async: asyncTime },\n total: total,\n };\n\n return results;\n }\n\n async runAndRecord(params, onProgress) {\n const measuredValues = {\n tests: {},\n total: 0,\n };\n const suiteStartLabel = `suite-${this.name}-start`;\n const suiteEndLabel = `suite-${this.name}-end`;\n\n performance.mark(suiteStartLabel);\n\n for (const test of this.tests) {\n const result = await test.runAndRecord(params, this, test, this.record);\n measuredValues.tests[test.name] = result;\n measuredValues.total += result.total;\n onProgress?.(test.name);\n }\n\n performance.mark(suiteEndLabel);\n performance.measure(`suite-${this.name}`, suiteStartLabel, suiteEndLabel);\n\n return {\n type: \"suite-tests-complete\",\n status: \"success\",\n result: measuredValues,\n suitename: this.name,\n };\n }\n}\n\n/**\n * BenchmarkSuitesManager\n *\n * A collection of test suites for a single workload.\n */\nexport class BenchmarkSuitesManager {\n constructor(name, suites) {\n this.name = name;\n this.suites = suites;\n }\n\n getSuiteByName(name) {\n return this.suites.find((suite) => suite.name === name);\n }\n}\n\n/**\n * Helper Methods\n *\n * Various methods that are extracted from the Page class.\n */\nexport function getParent(lookupStartNode, path) {\n lookupStartNode = lookupStartNode.shadowRoot ?? lookupStartNode;\n const parent = path.reduce((root, selector) => {\n const node = root.querySelector(selector);\n return node.shadowRoot ?? node;\n }, lookupStartNode);\n\n return parent;\n}\n\nexport function getElement(selector, path = [], lookupStartNode = document) {\n const element = getParent(lookupStartNode, path).querySelector(selector);\n return element;\n}\n\nexport function getAllElements(selector, path = [], lookupStartNode = document) {\n const elements = Array.from(getParent(lookupStartNode, path).querySelectorAll(selector));\n return elements;\n}\n\nexport function forceLayout() {\n const rect = document.body.getBoundingClientRect();\n const e = document.elementFromPoint((rect.width / 2) | 0, (rect.height / 2) | 0);\n return e;\n}\n\n/** **********************************************************************\n * Benchmark Connector\n *\n * postMessage is used to communicate between app and benchmark.\n * When the app os ready, an 'app-ready' message is sent to signal that the app can receive instructions.\n *\n * A prepare script within the apps appends window.name and window.version from the package.json file.\n * The appId is build by appending name-version\n * It's used as an additional safe-guard to ensure the correct app responds to a message.\n *************************************************************************/\nexport function connectFromRemote(name, version) {\n const appId = name && version ? `${name}-${version}` : -1;\n\n function sendMessage(message) {\n window.top.postMessage(message, \"*\");\n }\n\n window.onmessage = async (event) => {\n // ensure we only let legit functions run...\n if (event.data.id !== appId || event.data.key !== \"benchmark-connector\")\n return;\n\n switch (event.data.type) {\n case \"benchmark-suite\":\n // eslint-disable-next-line no-case-declarations\n const { result } = await window.benchmarkSuitesManager.getSuiteByName(event.data.name).runAndRecord(params, (test) => sendMessage({ type: \"step-complete\", status: \"success\", appId, name, test }));\n sendMessage({ type: \"suite-complete\", status: \"success\", appId, result });\n break;\n }\n };\n\n // Initialize the workload\n sendMessage({ type: \"app-ready\", status: \"success\", appId });\n}\n","\nimport { setupCounter } from \"./counter.js\";\nimport { BenchmarkTestStep, BenchmarkTestSuite, BenchmarkSuitesManager, forceLayout, getElement, connectFromRemote } from \"speedometer/resources/workload-testing-utils.mjs\";\n\nwindow.benchmarkSuitesManager = new BenchmarkSuitesManager(window.name, [\n new BenchmarkTestSuite(\"default\", [\n new BenchmarkTestStep(\"Click counter (1000)\", () => {\n for (let i = 0; i < 1000; i++) {\n getElement(\"#counter\").click();\n forceLayout();\n }\n }),\n new BenchmarkTestStep(\"Click counter (5000)\", () => {\n for (let i = 0; i < 5000; i++) {\n getElement(\"#counter\").click();\n forceLayout();\n }\n }),\n ]),\n]);\n\ndocument.querySelector(\"#app\").innerHTML = `\n
\n

Remote Workload - Hello World

\n
\n \n
\n
\n`;\n\nsetupCounter(document.querySelector(\"#counter\"));\n\nconnectFromRemote(\"remote-hello-world\", 1);\n"],"names":["params","searchParams","_test"],"mappings":"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"} \ No newline at end of file diff --git a/resources/remote-helloworld/dist/index.html b/resources/remote-helloworld/dist/index.html new file mode 100644 index 000000000..b9b4cf321 --- /dev/null +++ b/resources/remote-helloworld/dist/index.html @@ -0,0 +1,89 @@ + + + + + + Remote Workload - Hello World + + + + +
+ + diff --git a/resources/remote-helloworld/index.html b/resources/remote-helloworld/index.html new file mode 100644 index 000000000..a66ce728d --- /dev/null +++ b/resources/remote-helloworld/index.html @@ -0,0 +1,89 @@ + + + + + + Remote Workload - Hello World + + + +
+ + + diff --git a/resources/remote-helloworld/main.js b/resources/remote-helloworld/main.js new file mode 100644 index 000000000..7c6b38138 --- /dev/null +++ b/resources/remote-helloworld/main.js @@ -0,0 +1,33 @@ + +import { setupCounter } from "./counter.js"; +import { BenchmarkTestStep, BenchmarkTestSuite, BenchmarkSuitesManager, forceLayout, getElement, connectFromRemote } from "speedometer/resources/workload-testing-utils.mjs"; + +window.benchmarkSuitesManager = new BenchmarkSuitesManager(window.name, [ + new BenchmarkTestSuite("default", [ + new BenchmarkTestStep("Click counter (1000)", () => { + for (let i = 0; i < 1000; i++) { + getElement("#counter").click(); + forceLayout(); + } + }), + new BenchmarkTestStep("Click counter (5000)", () => { + for (let i = 0; i < 5000; i++) { + getElement("#counter").click(); + forceLayout(); + } + }), + ]), +]); + +document.querySelector("#app").innerHTML = ` +
+

Remote Workload - Hello World

+
+ +
+
+`; + +setupCounter(document.querySelector("#counter")); + +connectFromRemote("remote-hello-world", 1); diff --git a/resources/remote-helloworld/package-lock.json b/resources/remote-helloworld/package-lock.json new file mode 100644 index 000000000..f3dd440de --- /dev/null +++ b/resources/remote-helloworld/package-lock.json @@ -0,0 +1,870 @@ +{ + "name": "remote-helloworld", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "remote-helloworld", + "version": "0.0.0", + "dependencies": { + "speedometer": "../../" + }, + "devDependencies": { + "vite": "^5.4.10" + } + }, + "../..": { + "version": "3.0.0-alpha", + "license": "SEE LICENSE IN LICENSE", + "devDependencies": { + "@babel/core": "^7.21.3", + "@babel/eslint-parser": "^7.21.3", + "@babel/plugin-proposal-decorators": "^7.21.0", + "@next/eslint-plugin-next": "^13.4.5", + "@nuxt/eslint-config": "^0.1.1", + "@typescript-eslint/eslint-plugin": "^5.59.0", + "@typescript-eslint/parser": "^5.59.0", + "command-line-args": "^5.2.1", + "command-line-usage": "^6.1.3", + "eslint": "^8.38.0", + "eslint-plugin-ember": "^11.4.8", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-vue": "^9.10.0", + "expect.js": "^0.3.1", + "http-server": "^14.1.1", + "mocha": "^10.2.0", + "prettier": "^2.8.3", + "selenium-webdriver": "^4.8.0", + "sinon": "^17.0.1", + "typescript": "^5.0.4" + }, + "engines": { + "node": ">=18.13.0", + "npm": ">=8.19.3" + } + }, + "../../..": {}, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.3.tgz", + "integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.3.tgz", + "integrity": "sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.3.tgz", + "integrity": "sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.3.tgz", + "integrity": "sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.3.tgz", + "integrity": "sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.3.tgz", + "integrity": "sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.3.tgz", + "integrity": "sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.3.tgz", + "integrity": "sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.3.tgz", + "integrity": "sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.3.tgz", + "integrity": "sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.3.tgz", + "integrity": "sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.3.tgz", + "integrity": "sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.3.tgz", + "integrity": "sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.3.tgz", + "integrity": "sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.3.tgz", + "integrity": "sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.3.tgz", + "integrity": "sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.3.tgz", + "integrity": "sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.3.tgz", + "integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.3.tgz", + "integrity": "sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.27.3", + "@rollup/rollup-android-arm64": "4.27.3", + "@rollup/rollup-darwin-arm64": "4.27.3", + "@rollup/rollup-darwin-x64": "4.27.3", + "@rollup/rollup-freebsd-arm64": "4.27.3", + "@rollup/rollup-freebsd-x64": "4.27.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.27.3", + "@rollup/rollup-linux-arm-musleabihf": "4.27.3", + "@rollup/rollup-linux-arm64-gnu": "4.27.3", + "@rollup/rollup-linux-arm64-musl": "4.27.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.27.3", + "@rollup/rollup-linux-riscv64-gnu": "4.27.3", + "@rollup/rollup-linux-s390x-gnu": "4.27.3", + "@rollup/rollup-linux-x64-gnu": "4.27.3", + "@rollup/rollup-linux-x64-musl": "4.27.3", + "@rollup/rollup-win32-arm64-msvc": "4.27.3", + "@rollup/rollup-win32-ia32-msvc": "4.27.3", + "@rollup/rollup-win32-x64-msvc": "4.27.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speedometer": { + "resolved": "../..", + "link": true + }, + "node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/resources/remote-helloworld/package.json b/resources/remote-helloworld/package.json new file mode 100644 index 000000000..4502f92cc --- /dev/null +++ b/resources/remote-helloworld/package.json @@ -0,0 +1,17 @@ +{ + "name": "remote-helloworld", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "speedometer": "../../" + }, + "devDependencies": { + "vite": "^5.4.10" + } +} diff --git a/resources/remote-helloworld/vite.config.js b/resources/remote-helloworld/vite.config.js new file mode 100644 index 000000000..4bccc1c75 --- /dev/null +++ b/resources/remote-helloworld/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from "vite"; +export default defineConfig({ + base: "./", // Since this will be loaded from the project root + build: { + modulePreload: { polyfill: false }, + minify: false, + sourcemap: true, + }, +}); diff --git a/resources/suite-runner.mjs b/resources/suite-runner.mjs index 16bc2cb51..5bde39949 100644 --- a/resources/suite-runner.mjs +++ b/resources/suite-runner.mjs @@ -1,8 +1,6 @@ import { TestRunner } from "./test-runner.mjs"; import { WarmupSuite } from "./benchmark-runner.mjs"; -// FIXME: Create AsyncSuiteRunner subclass. -// FIXME: Create RemoteSuiteRunner subclass. export class SuiteRunner { #frame; #page; @@ -25,6 +23,30 @@ export class SuiteRunner { this.#params = params; } + get frame() { + return this.#frame; + } + + get page() { + return this.#page; + } + + get params() { + return this.#params; + } + + get suite() { + return this.#suite; + } + + get client() { + return this.#client; + } + + get suiteResults() { + return this.#suiteResults; + } + async run() { await this._prepareSuite(); await this._runSuite(); @@ -76,7 +98,7 @@ export class SuiteRunner { const frame = this.#frame; frame.onload = () => resolve(); frame.onerror = () => reject(); - frame.src = this.#suite.url; + frame.src = `${this.#suite.url}?${this.#params.toSearchParams(true)}`; }); } @@ -89,13 +111,101 @@ export class SuiteRunner { this.#suiteResults.tests[test.name] = { tests: { Sync: syncTime, Async: asyncTime }, total: total }; this.#suiteResults.total += total; - if (this.#client?.didRunTest) - await this.#client.didRunTest(this.#suite, test); + await this._updateClient(test); }; + + async _updateClient(test, suite = this.#suite) { + if (this.#client?.didRunTest) + await this.#client.didRunTest(suite, test); + } } -// FIXME: implement remote steps -class RemoteSuiteRunner extends SuiteRunner {} +export class RemoteSuiteRunner extends SuiteRunner { + #appId; + + get appId() { + return this.#appId; + } + + set appId(id) { + this.#appId = id; + } + + async run() { + this.postMessageCallbacks = new Map(); + const handler = this._handlePostMessage.bind(this); + window.addEventListener("message", handler); + + // FIXME: use this._suite in all SuiteRunner methods directly. + await this._prepareSuite(); + await this._runSuite(); + + window.removeEventListener("message", handler); + } + + async _prepareSuite() { + const suiteName = this.suite.name; + const suitePrepareStartLabel = `suite-${suiteName}-prepare-start`; + const suitePrepareEndLabel = `suite-${suiteName}-prepare-end`; + + performance.mark(suitePrepareStartLabel); + + // Wait for the app-ready message from the workload. + const promise = this._subscribeOnce("app-ready"); + await this._loadFrame(this.suite); + const response = await promise; + await this.suite.prepare(this.page); + // Capture appId to pass along with messages. + this.appId = response?.appId; + + performance.mark(suitePrepareEndLabel); + + performance.measure(`suite-${suiteName}-prepare`, suitePrepareStartLabel, suitePrepareEndLabel); + } + + async _runSuite() { + // Update progress bar with each completed step. + this._startSubscription("step-complete", async (e) => this._updateClient(e.data.test, e.data.name)); + // Ask workload to run its own tests. + this.frame.contentWindow.postMessage({ id: this.appId, key: "benchmark-connector", type: "benchmark-suite", name: this.suite.config?.name || "default" }, "*"); + // Capture metrics from the completed tests. + const response = await this._subscribeOnce("suite-complete"); + this._stopSubscription("step-complete"); + + this.suiteResults.tests = response.result.tests; + this.suiteResults.total += response.result.total; + + this._validateSuiteTotal(); + } + + _handlePostMessage(e) { + if (this.postMessageCallbacks.has(e.data.type)) + this.postMessageCallbacks.get(e.data.type)(e); + } + + _startSubscription(type, callback) { + if (this.postMessageCallbacks.has(type)) + throw new Error("Callback exists already"); + + this.postMessageCallbacks.set(type, callback); + } + + _stopSubscription(type) { + if (!this.postMessageCallbacks.has(type)) + throw new Error("Callback does not exist"); + + this.postMessageCallbacks.delete(type); + } + + _subscribeOnce(type) { + return new Promise((resolve) => { + this._startSubscription(type, (e) => { + this._stopSubscription(type); + resolve(e.data); + }); + }); + } +} export const SUITE_RUNNER_LOOKUP = { __proto__: null, diff --git a/resources/test-invoker.mjs b/resources/test-invoker.mjs index 672c69ae0..cebd5271c 100644 --- a/resources/test-invoker.mjs +++ b/resources/test-invoker.mjs @@ -15,8 +15,8 @@ export class TimerTestInvoker extends TestInvoker { setTimeout(() => { this._asyncCallback(); requestAnimationFrame(async () => { - await this._reportCallback(); - resolve(); + const result = await this._reportCallback(); + resolve(result); }); }, 0); }, this._params.waitBeforeSync); @@ -40,8 +40,8 @@ export class RAFTestInvoker extends TestInvoker { setTimeout(() => { this._asyncCallback(); setTimeout(async () => { - await this._reportCallback(); - resolve(); + const result = await this._reportCallback(); + resolve(result); }, 0); }, 0); }); diff --git a/resources/tests.mjs b/resources/tests.mjs index 66eabae11..764a4767d 100644 --- a/resources/tests.mjs +++ b/resources/tests.mjs @@ -1101,6 +1101,19 @@ Suites.push({ ], }); +Suites.push({ + name: "Remote-Helloworld", + url: "resources/remote-helloworld/dist/index.html", + tags: ["experimental"], + disabled: true, + async prepare() {}, + type: "remote", + config: { + // name: "default", // optional param to target non-default tests locally + steps: 2, // manual step for the progress display on the bottom: this is the number of BenchmarkSteps in a BenchmarkSuite. + }, +}); + Object.freeze(Suites); Suites.forEach((suite) => { if (!suite.tags) diff --git a/resources/workload-testing-utils.mjs b/resources/workload-testing-utils.mjs new file mode 100644 index 000000000..9485b83c8 --- /dev/null +++ b/resources/workload-testing-utils.mjs @@ -0,0 +1,156 @@ +/* + This is loaded by the remote test page and provides utilities & client-side communication with the test runner. +*/ + +import { TestRunner } from "./test-runner.mjs"; +import { params } from "./params.mjs"; + +/** + * BenchmarkTestStep + * + * A single test step, with a common interface to interact with. + */ +export class BenchmarkTestStep { + constructor(name, run) { + this.name = name; + this.run = run; + } + + async runAndRecord(params, suite, test, callback) { + const testRunner = new TestRunner(null, null, params, suite, test, callback); + const result = await testRunner.runTest(); + return result; + } +} + +/** + * BenchmarkTestSuite + * + * A single test suite that contains one or more test steps. + */ +export class BenchmarkTestSuite { + constructor(name, tests) { + this.name = name; + this.tests = tests; + } + + record(_test, syncTime, asyncTime) { + const total = syncTime + asyncTime; + const results = { + tests: { Sync: syncTime, Async: asyncTime }, + total: total, + }; + + return results; + } + + async runAndRecord(params, onProgress) { + const measuredValues = { + tests: {}, + total: 0, + }; + const suiteStartLabel = `suite-${this.name}-start`; + const suiteEndLabel = `suite-${this.name}-end`; + + performance.mark(suiteStartLabel); + + for (const test of this.tests) { + const result = await test.runAndRecord(params, this, test, this.record); + measuredValues.tests[test.name] = result; + measuredValues.total += result.total; + onProgress?.(test.name); + } + + performance.mark(suiteEndLabel); + performance.measure(`suite-${this.name}`, suiteStartLabel, suiteEndLabel); + + return { + type: "suite-tests-complete", + status: "success", + result: measuredValues, + suitename: this.name, + }; + } +} + +/** + * BenchmarkSuitesManager + * + * A collection of test suites for a single workload. + */ +export class BenchmarkSuitesManager { + constructor(name, suites) { + this.name = name; + this.suites = suites; + } + + getSuiteByName(name) { + return this.suites.find((suite) => suite.name === name); + } +} + +/** + * Helper Methods + * + * Various methods that are extracted from the Page class. + */ +export function getParent(lookupStartNode, path) { + lookupStartNode = lookupStartNode.shadowRoot ?? lookupStartNode; + const parent = path.reduce((root, selector) => { + const node = root.querySelector(selector); + return node.shadowRoot ?? node; + }, lookupStartNode); + + return parent; +} + +export function getElement(selector, path = [], lookupStartNode = document) { + const element = getParent(lookupStartNode, path).querySelector(selector); + return element; +} + +export function getAllElements(selector, path = [], lookupStartNode = document) { + const elements = Array.from(getParent(lookupStartNode, path).querySelectorAll(selector)); + return elements; +} + +export function forceLayout() { + const rect = document.body.getBoundingClientRect(); + const e = document.elementFromPoint((rect.width / 2) | 0, (rect.height / 2) | 0); + return e; +} + +/** ********************************************************************** + * Benchmark Connector + * + * postMessage is used to communicate between app and benchmark. + * When the app os ready, an 'app-ready' message is sent to signal that the app can receive instructions. + * + * A prepare script within the apps appends window.name and window.version from the package.json file. + * The appId is build by appending name-version + * It's used as an additional safe-guard to ensure the correct app responds to a message. + *************************************************************************/ +export function connectFromRemote(name, version) { + const appId = name && version ? `${name}-${version}` : -1; + + function sendMessage(message) { + window.top.postMessage(message, "*"); + } + + window.onmessage = async (event) => { + // ensure we only let legit functions run... + if (event.data.id !== appId || event.data.key !== "benchmark-connector") + return; + + switch (event.data.type) { + case "benchmark-suite": + // eslint-disable-next-line no-case-declarations + const { result } = await window.benchmarkSuitesManager.getSuiteByName(event.data.name).runAndRecord(params, (test) => sendMessage({ type: "step-complete", status: "success", appId, name, test })); + sendMessage({ type: "suite-complete", status: "success", appId, result }); + break; + } + }; + + // Initialize the workload + sendMessage({ type: "app-ready", status: "success", appId }); +}